/**
 * System Config Service
 *
 * Service for managing system-wide configurations with encryption,
 * change tracking, and data type parsing capabilities.
 */

import { PrismaClient, Prisma } from '@prisma/client';
import { SystemConfigInput } from './dtos/system-config.dto';
import crypto from 'crypto';

const ENCRYPTION_KEY = process.env.CONFIG_ENCRYPTION_KEY || '';
const ENCRYPTION_IV_LENGTH = 16;
const ENCRYPTION_ALGORITHM = 'aes-256-cbc';

interface ChangeRecord {
  value: string;
  date: string;
  user?: string | null;
}

export class SystemConfigService {
  constructor(private prisma: PrismaClient) {}

  /**
   * Obtener config por key (con desencriptación si es necesario)
   */
  async get(key: string, decrypt: boolean = false): Promise<string | null> {
    const config = await this.prisma.systemConfig.findUnique({
      where: { key },
    });

    if (!config) return null;

    if (decrypt && config.isEncrypted) {
      return this.decryptValue(config.value);
    }

    return config.value;
  }

  /**
   * Obtener config por key con valor parseado según dataType
   */
  async getParsed(key: string, decrypt: boolean = false): Promise<any> {
    const config = await this.prisma.systemConfig.findUnique({
      where: { key },
    });

    if (!config) return null;

    let value = config.value;
    if (decrypt && config.isEncrypted) {
      value = this.decryptValue(value);
    }

    return this.parseValue(value, config.dataType);
  }

  /**
   * Obtener múltiples configs por categoría
   */
  async getByCategory(
    category: string,
    includePrivate: boolean = false
  ): Promise<Array<{ key: string; value: string; description: string | null; dataType: string }>> {
    return this.prisma.systemConfig.findMany({
      where: {
        category,
        isPublic: includePrivate ? undefined : true,
      },
      select: {
        key: true,
        value: true,
        description: true,
        dataType: true,
      },
    });
  }

  /**
   * Crear o actualizar config
   */
  async upsert(
    data: SystemConfigInput,
    userId?: string
  ): Promise<void> {
    const { key, value, ...rest } = data;

    const existing = await this.prisma.systemConfig.findUnique({
      where: { key },
    });

    const finalValue = rest.isEncrypted
      ? this.encryptValue(value)
      : value;

    const changeRecord: ChangeRecord | null = existing
      ? {
          value: existing.value,
          date: new Date().toISOString(),
          user: existing.updatedBy ?? existing.createdBy ?? null,
        }
      : null;

    const updateData: Prisma.SystemConfigUpdateInput = {
      value: finalValue,
      ...rest,
      updatedBy: userId,
    };

    if (changeRecord && existing) {
      const currentHistory = this.parseChangeHistory(existing.changeHistory);
      updateData.changeHistory = [...currentHistory, changeRecord];
    }

    await this.prisma.systemConfig.upsert({
      where: { key },
      update: updateData,
      create: {
        key,
        value: finalValue,
        ...rest,
        createdBy: userId,
        updatedBy: userId,
        changeHistory: [],
      },
    });
  }

  /**
   * Actualizar solo el valor de una config existente
   */
  async updateValue(
    key: string,
    value: string,
    userId?: string
  ): Promise<void> {
    const existing = await this.prisma.systemConfig.findUnique({
      where: { key },
    });

    if (!existing) {
      throw new Error(`Config with key '${key}' not found`);
    }

    const finalValue = existing.isEncrypted
      ? this.encryptValue(value)
      : value;

    const changeRecord: ChangeRecord = {
      value: existing.value,
      date: new Date().toISOString(),
      user: userId ?? null,
    };

    const currentHistory = this.parseChangeHistory(existing.changeHistory);

    await this.prisma.systemConfig.update({
      where: { key },
      data: {
        value: finalValue,
        updatedBy: userId,
        changeHistory: [...currentHistory, changeRecord],
      },
    });
  }

  /**
   * Eliminar una config
   */
  async delete(key: string): Promise<void> {
    await this.prisma.systemConfig.delete({
      where: { key },
    });
  }

  /**
   * Obtener todas las configs públicas (para frontend)
   */
  async getPublicConfigs(): Promise<
    Array<{
      key: string;
      value: string;
      description: string | null;
      category: string | null;
      dataType: string;
    }>
  > {
    return this.prisma.systemConfig.findMany({
      where: { isPublic: true },
      select: {
        key: true,
        value: true,
        description: true,
        category: true,
        dataType: true,
      },
    });
  }

  /**
   * Obtener todas las configs (solo admin)
   */
  async getAllConfigs(): Promise<
    Array<{
      id: string;
      key: string;
      value: string;
      description: string | null;
      category: string | null;
      isPublic: boolean;
      isEncrypted: boolean;
      dataType: string;
      createdAt: Date;
      updatedAt: Date;
      createdBy: string | null;
      updatedBy: string | null;
    }>
  > {
    return this.prisma.systemConfig.findMany({
      orderBy: { key: 'asc' },
    });
  }

  /**
   * Obtener el historial de cambios de una config
   */
  async getChangeHistory(key: string): Promise<ChangeRecord[]> {
    const config = await this.prisma.systemConfig.findUnique({
      where: { key },
      select: { changeHistory: true },
    });

    return this.parseChangeHistory(config?.changeHistory);
  }

  /**
   * Parse and validate change history from JSON value
   */
  private parseChangeHistory(changeHistory: unknown): ChangeRecord[] {
    if (!changeHistory || !Array.isArray(changeHistory)) {
      return [];
    }
    
    return changeHistory.filter((item): item is ChangeRecord => {
      if (!item || typeof item !== 'object') return false;
      const record = item as Record<string, unknown>;
      return typeof record.value === 'string' && typeof record.date === 'string';
    });
  }

  /**
   * Parsear valor según dataType
   */
  parseValue(value: string, dataType: string): any {
    switch (dataType) {
      case 'number':
        return parseFloat(value);
      case 'boolean':
        return value === 'true' || value === '1';
      case 'json':
        try {
          return JSON.parse(value);
        } catch {
          return value;
        }
      case 'date':
        return new Date(value);
      default:
        return value;
    }
  }

  /**
   * Convertir valor a string según dataType
   */
  stringifyValue(value: any, dataType: string): string {
    switch (dataType) {
      case 'json':
        return JSON.stringify(value);
      default:
        return String(value);
    }
  }

  private encryptValue(value: string): string {
    if (!ENCRYPTION_KEY || ENCRYPTION_KEY.length === 0) {
      throw new Error('Encryption key not configured (CONFIG_ENCRYPTION_KEY)');
    }

    const key = crypto
      .createHash('sha256')
      .update(ENCRYPTION_KEY)
      .digest();

    const iv = crypto.randomBytes(ENCRYPTION_IV_LENGTH);
    const cipher = crypto.createCipheriv(ENCRYPTION_ALGORITHM, key, iv);

    let encrypted = cipher.update(value, 'utf8', 'hex');
    encrypted += cipher.final('hex');

    return iv.toString('hex') + ':' + encrypted;
  }

  private decryptValue(encrypted: string): string {
    if (!ENCRYPTION_KEY || ENCRYPTION_KEY.length === 0) {
      throw new Error('Encryption key not configured (CONFIG_ENCRYPTION_KEY)');
    }

    const key = crypto
      .createHash('sha256')
      .update(ENCRYPTION_KEY)
      .digest();

    const parts = encrypted.split(':');
    if (parts.length !== 2) {
      throw new Error('Invalid encrypted value format');
    }

    const iv = Buffer.from(parts[0], 'hex');
    const encryptedValue = parts[1];

    const decipher = crypto.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);

    let decrypted = decipher.update(encryptedValue, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  }
}
