/**
 * Admin Settings Routes
 *
 * Admin endpoints for managing system settings
 */

import { Router, Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import { AdminRole } from '../../types';
import { authenticateAdmin } from '../../middleware/auth';
import { logger } from '../../utils/logger';
import { z } from 'zod';
import { refreshConfigFromDatabase, publishConfigUpdate, ConfigChangeEvent } from '../../config';

/**
 * Map setting keys to config categories for refresh
 */
const SETTING_TO_CONFIG_MAP: Record<string, ConfigChangeEvent> = {
  'gemini_api_key': 'gemini',
  'paypal_client_id': 'paypal',
  'paypal_client_secret': 'paypal',
  'paypal_webhook_id': 'paypal',
  'paypal_mode': 'paypal',
  'pricing_per_1k_characters': 'pricing',
};

/**
 * Determine which config categories need to be refreshed based on updated setting keys
 */
function getConfigCategoriesToRefresh(settingKeys: string[]): ConfigChangeEvent[] {
  const categories = new Set<ConfigChangeEvent>();
  for (const key of settingKeys) {
    const category = SETTING_TO_CONFIG_MAP[key];
    if (category) {
      categories.add(category);
    }
  }
  return Array.from(categories);
}

const SENSITIVE_SETTING_KEYS = new Set([
  'gemini_api_key',
  'paypal_client_id',
  'paypal_client_secret',
  'paypal_webhook_id',
]);
const SENSITIVE_SETTING_KEY_PATTERN = /(?:api_key|client_secret|password|private_key|access_token|refresh_token)$/i;

function isSensitiveSetting(key: string): boolean {
  return SENSITIVE_SETTING_KEYS.has(key) || SENSITIVE_SETTING_KEY_PATTERN.test(key);
}

export function redactSettingValue(key: string, value: unknown): unknown {
  return isSensitiveSetting(key) ? '[REDACTED]' : value;
}

function serializeSetting(setting: {
  id: string;
  key: string;
  value: unknown;
  description: string;
  updated_at: Date;
  updated_by: string | null;
  admin?: { email: string } | null;
}): SystemSettingResponse {
  const sensitive = isSensitiveSetting(setting.key);
  return {
    id: setting.id,
    key: setting.key,
    ...(!sensitive ? { value: setting.value } : {}),
    description: setting.description,
    updatedAt: setting.updated_at.toISOString(),
    updated_by: setting.updated_by,
    updated_byEmail: setting.admin?.email,
    ...(sensitive ? { configured: setting.value !== null && setting.value !== undefined && setting.value !== '' } : {}),
  };
}

function requireSettingsAdmin(req: Request, res: Response, next: NextFunction): void {
  if (req.admin?.role !== AdminRole.ADMIN) {
    res.status(403).json({
      error: true,
      code: 'FORBIDDEN',
      message: 'Administrator role required',
      timestamp: new Date().toISOString(),
    });
    return;
  }
  next();
}

class ConfigPropagationError extends Error {
  constructor(cause: unknown) {
    super('Configuration propagation failed', { cause });
    this.name = 'ConfigPropagationError';
  }
}

export async function propagateConfigUpdates(
  settingKeys: string[],
  settingVersions: Record<string, string> = {}
): Promise<ConfigChangeEvent[]> {
  const categories = getConfigCategoriesToRefresh(settingKeys);
  if (categories.length === 0) {
    return categories;
  }

  const applicableVersions = Object.fromEntries(
    settingKeys
      .filter((key) => SETTING_TO_CONFIG_MAP[key])
      .map((key) => [key, settingVersions[key]])
  );
  if (
    Object.keys(applicableVersions).length === 0 ||
    Object.values(applicableVersions).some((version) => typeof version !== 'string')
  ) {
    throw new ConfigPropagationError(new Error('Setting versions are required'));
  }

  const results = await Promise.allSettled([
    refreshConfigFromDatabase(categories),
    publishConfigUpdate(categories, undefined, { settingVersions: applicableVersions }),
  ]);
  const failures = results
    .filter((result): result is PromiseRejectedResult => result.status === 'rejected')
    .map((result) => result.reason);

  if (failures.length > 0) {
    logger.error('Failed to propagate updated configuration', { failures, categories });
    throw new ConfigPropagationError(new AggregateError(failures));
  }

  return categories;
}

const router = Router();
const prisma = new PrismaClient();

/**
 * System setting response
 */
interface SystemSettingResponse {
  id: string;
  key: string;
  value?: unknown;
  description: string;
  updatedAt: string;
  updated_by: string | null;
  updated_byEmail?: string;
  configured?: boolean;
}


/**
 * Validation schema for settings update
 */
const settingsUpdateSchema = z.object({
  settings: z.array(
    z.object({
      key: z.string().min(1),
      value: z.unknown(),
    })
  ),
});

/**
 * Known system settings with their validation rules
 */
const KNOWN_SETTINGS = {
  // Gemini API settings
  'gemini_api_key': {
    description: 'Google Gemini API key for translation services',
    schema: z.string().min(1),
  },
  // PayPal settings
  'paypal_client_id': {
    description: 'PayPal Client ID',
    schema: z.string().min(1),
  },
  'paypal_client_secret': {
    description: 'PayPal Client Secret',
    schema: z.string().min(1),
  },
  'paypal_webhook_id': {
    description: 'PayPal Webhook ID',
    schema: z.string().min(1),
  },
  'paypal_mode': {
    description: 'PayPal mode (sandbox or live)',
    schema: z.enum(['sandbox', 'live']),
  },
  // Pricing settings
  'pricing_per_1k_characters': {
    description: 'Price per 1000 characters for translation (USD)',
    schema: z.number().min(0),
  },
  // Subscription pricing
  'subscription_price_starter': {
    description: 'Subscription price for starter tier (USD)',
    schema: z.number().min(0),
  },
  'subscription_price_professional': {
    description: 'Subscription price for professional tier (USD)',
    schema: z.number().min(0),
  },
  'subscription_price_enterprise': {
    description: 'Subscription price for enterprise tier (USD)',
    schema: z.number().min(0),
  },
  // Legacy settings (backward compatibility)
  'rate_limiting.enabled': {
    description: 'Enable or disable API rate limiting',
    schema: z.boolean(),
  },
  'rate_limiting.default_rpm': {
    description: 'Default rate limit (requests per minute) for new users',
    schema: z.number().int().min(0),
  },
  'translation.timeout_ms': {
    description: 'Translation request timeout in milliseconds',
    schema: z.number().int().min(1000).max(300000),
  },
  'translation.max_content_length': {
    description: 'Maximum content length in characters',
    schema: z.number().int().min(100).max(100000),
  },
  'webhooks.enabled': {
    description: 'Enable or disable webhook delivery',
    schema: z.boolean(),
  },
  'webhooks.max_retries': {
    description: 'Maximum number of webhook delivery retries',
    schema: z.number().int().min(0).max(10),
  },
  'webhooks.retry_delay_ms': {
    description: 'Delay between webhook retry attempts in milliseconds',
    schema: z.number().int().min(1000).max(300000),
  },
  'maintenance.enabled': {
    description: 'Enable maintenance mode',
    schema: z.boolean(),
  },
  'maintenance.message': {
    description: 'Maintenance mode message',
    schema: z.string(),
  },
  'email.verification_required': {
    description: 'Require email verification for new accounts',
    schema: z.boolean(),
  },
  'security.max_login_attempts': {
    description: 'Maximum login attempts before account lockout',
    schema: z.number().int().min(3).max(10),
  },
  'security.lockout_duration_minutes': {
    description: 'Account lockout duration in minutes',
    schema: z.number().int().min(5).max(1440),
  },
};

/**
 * GET /v1/admin/settings
 * Get all system settings
 */
router.get('/', authenticateAdmin, requireSettingsAdmin, async (_req: Request, res: Response) => {
  try {
    const settings = await prisma.systemSetting.findMany({
      where: { key: { in: Object.keys(KNOWN_SETTINGS) } },
      include: {
        admin: {
          select: {
            email: true,
          },
        },
      },
      orderBy: { key: 'asc' },
    });

    const response: SystemSettingResponse[] = settings.map(serializeSetting);

    res.json(response);
  } catch (error) {
    logger.error('Error fetching system settings', { error });
    res.status(500).json({
      error: true,
      code: 'INTERNAL_ERROR',
      message: 'Failed to fetch system settings',
      timestamp: new Date().toISOString(),
    });
  }
});

/**
 * GET /v1/admin/settings/:key
 * Get a specific system setting by key
 */
router.get('/:key', authenticateAdmin, requireSettingsAdmin, async (req: Request, res: Response) => {
  try {
    const { key } = req.params;
    if (!(key in KNOWN_SETTINGS)) {
      res.status(404).json({
        error: true,
        code: 'SETTING_NOT_FOUND',
        message: `Setting with key '${key}' not found`,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    const setting = await prisma.systemSetting.findUnique({
      where: { key },
      include: {
        admin: {
          select: {
            email: true,
          },
        },
      },
    });

    if (!setting) {
      res.status(404).json({
        error: true,
        code: 'SETTING_NOT_FOUND',
        message: `Setting with key '${key}' not found`,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    const response: SystemSettingResponse = serializeSetting(setting);

    res.json(response);
  } catch (error) {
    logger.error('Error fetching system setting', { error, key: req.params.key });
    res.status(500).json({
      error: true,
      code: 'INTERNAL_ERROR',
      message: 'Failed to fetch system setting',
      timestamp: new Date().toISOString(),
    });
  }
});

/**
 * PATCH /v1/admin/settings
 * Update system settings (batch update)
 */
router.patch('/', authenticateAdmin, requireSettingsAdmin, async (req: Request, res: Response) => {
  try {
    // Validate request body
    const validation = settingsUpdateSchema.safeParse(req.body);
    if (!validation.success) {
      res.status(400).json({
        error: true,
        code: 'VALIDATION_ERROR',
        message: 'Invalid request body',
        errors: validation.error.errors,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    const { settings } = validation.data;
    const adminId = req.admin!.id;

    // Check if adminId is a valid UUID (for dev mode compatibility)
    const isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(adminId);
    const updatedBy = isValidUuid ? adminId : null;

    // Validate each setting
    const validationErrors: Array<{ key: string; message: string }> = [];
    const validSettings: Array<{ key: string; value: unknown }> = [];

    for (const setting of settings) {
      const knownSetting = KNOWN_SETTINGS[setting.key as keyof typeof KNOWN_SETTINGS];

      if (!knownSetting) {
        validationErrors.push({
          key: setting.key,
          message: `Unknown setting key: ${setting.key}`,
        });
        continue;
      }

      const valueValidation = knownSetting.schema.safeParse(setting.value);
      if (!valueValidation.success) {
        validationErrors.push({
          key: setting.key,
          message: `Invalid value for ${setting.key}: ${valueValidation.error.errors[0]?.message}`,
        });
        continue;
      }

      validSettings.push({
        key: setting.key,
        value: valueValidation.data,
      });
    }

    if (validationErrors.length > 0) {
      res.status(400).json({
        error: true,
        code: 'VALIDATION_ERROR',
        message: 'One or more settings have invalid values',
        errors: validationErrors,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    // Update settings in transaction
    const updatedSettings = await prisma.$transaction(async (tx) => {
      const results = [];

      for (const setting of validSettings) {
        const knownSetting = KNOWN_SETTINGS[setting.key as keyof typeof KNOWN_SETTINGS];

        // Upsert the setting
        const updated = await tx.systemSetting.upsert({
          where: { key: setting.key },
          update: {
            value: setting.value as any,
            updated_by: updatedBy,
          },
          create: {
            key: setting.key,
            value: setting.value as any,
            description: knownSetting!.description,
            updated_by: updatedBy,
          },
        });

        // Log the change in audit log (skip if no valid admin ID - dev mode)
        if (isValidUuid) {
          await tx.auditLog.create({
            data: {
              action: 'system_setting.updated',
              resource_type: 'system_setting',
              resource_id: updated.id,
              details: {
                key: setting.key,
                oldValue: null,
                newValue: redactSettingValue(setting.key, setting.value),
              } as any,
              ip_address: req.ip,
              user_agent: req.get('user-agent'),
            },
          });
        }

        results.push(updated);
      }

      return results;
    });

    // Format response
    const response: SystemSettingResponse[] = updatedSettings.map(serializeSetting);

    logger.info('System settings updated', {
      adminId,
      adminEmail: req.admin!.email,
      settingsUpdated: validSettings.map((s) => s.key),
    });

    let categoriesToRefresh: ConfigChangeEvent[];
    try {
      categoriesToRefresh = await propagateConfigUpdates(
        validSettings.map((s) => s.key),
        Object.fromEntries(updatedSettings.map((setting) => [
          setting.key,
          setting.updated_at.toISOString(),
        ]))
      );
    } catch (error) {
      if (!(error instanceof ConfigPropagationError)) {
        throw error;
      }

      res.status(503).json({
        error: true,
        code: 'CONFIG_PROPAGATION_FAILED',
        message: 'Settings were persisted, but runtime configuration propagation failed',
        settingsPersisted: true,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    if (categoriesToRefresh.length > 0) {
      logger.info('Config refreshed for categories', { categories: categoriesToRefresh });
    }

    res.json({
      success: true,
      message: `${validSettings.length} setting(s) updated successfully`,
      settings: response,
      configRefreshed: categoriesToRefresh.length > 0 ? categoriesToRefresh : undefined,
    });
  } catch (error) {
    logger.error('Error updating system settings', { error, adminId: req.admin?.id });
    res.status(500).json({
      error: true,
      code: 'INTERNAL_ERROR',
      message: 'Failed to update system settings',
      timestamp: new Date().toISOString(),
    });
  }
});

/**
 * PUT /v1/admin/settings/:key
 * Update a single system setting
 */
router.put('/:key', authenticateAdmin, requireSettingsAdmin, async (req: Request, res: Response) => {
  try {
    const { key } = req.params;
    const { value } = req.body;

    if (value === undefined) {
      res.status(400).json({
        error: true,
        code: 'VALIDATION_ERROR',
        message: 'Setting value is required',
        timestamp: new Date().toISOString(),
      });
      return;
    }

    const knownSetting = KNOWN_SETTINGS[key as keyof typeof KNOWN_SETTINGS];

    if (!knownSetting) {
      res.status(400).json({
        error: true,
        code: 'UNKNOWN_SETTING',
        message: `Unknown setting key: ${key}`,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    // Validate value
    const valueValidation = knownSetting.schema.safeParse(value);
    if (!valueValidation.success) {
      res.status(400).json({
        error: true,
        code: 'VALIDATION_ERROR',
        message: `Invalid value for ${key}`,
        errors: valueValidation.error.errors,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    const adminId = req.admin!.id;

    // Update setting in transaction
    const updated = await prisma.$transaction(async (tx) => {
      const setting = await tx.systemSetting.upsert({
        where: { key },
        update: {
          value: valueValidation.data,
          updated_by: adminId,
        },
        create: {
          key,
          value: valueValidation.data,
          description: knownSetting.description,
          updated_by: adminId,
        },
      });

      // Log the change
      await tx.auditLog.create({
        data: {
          action: 'system_setting.updated',
          resource_type: 'system_setting',
          resource_id: setting.id,
          details: {
            key,
            newValue: redactSettingValue(key, valueValidation.data),
          } as any,
          ip_address: req.ip,
          user_agent: req.get('user-agent'),
        },
      });

      return setting;
    });

    logger.info('System setting updated', {
      adminId,
      adminEmail: req.admin!.email,
      key,
      value: redactSettingValue(key, valueValidation.data),
    });

    let categoriesToRefresh: ConfigChangeEvent[];
    try {
      categoriesToRefresh = await propagateConfigUpdates([key], {
        [updated.key]: updated.updated_at.toISOString(),
      });
    } catch (error) {
      if (!(error instanceof ConfigPropagationError)) {
        throw error;
      }

      res.status(503).json({
        error: true,
        code: 'CONFIG_PROPAGATION_FAILED',
        message: 'Setting was persisted, but runtime configuration propagation failed',
        settingsPersisted: true,
        timestamp: new Date().toISOString(),
      });
      return;
    }

    if (categoriesToRefresh.length > 0) {
      logger.info('Config refreshed for categories', { categories: categoriesToRefresh });
    }

    const response: SystemSettingResponse = serializeSetting(updated);

    res.json(response);
  } catch (error) {
    logger.error('Error updating system setting', { error, key: req.params.key, adminId: req.admin?.id });
    res.status(500).json({
      error: true,
      code: 'INTERNAL_ERROR',
      message: 'Failed to update system setting',
      timestamp: new Date().toISOString(),
    });
  }
});

export default router;
