import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Configuration } from '../../entities/configuration.entity';
import * as crypto from 'crypto';

@Injectable()
export class AppConfigService implements OnModuleInit {
  private readonly logger = new Logger(AppConfigService.name);
  private cache: Map<string, any> = new Map();
  private readonly ENCRYPTION_KEY: string;
  private readonly ENCRYPTION_ALGORITHM = 'aes-256-cbc';
  private readonly IV_LENGTH = 16;

  constructor(
    @InjectRepository(Configuration)
    private configRepo: Repository<Configuration>,
  ) {
    // Clé de chiffrement depuis les variables d'environnement
    // En production, cette clé doit être générée de manière sécurisée
    this.ENCRYPTION_KEY =
      process.env.CONFIG_ENCRYPTION_KEY ||
      crypto.randomBytes(32).toString('hex');

    if (!process.env.CONFIG_ENCRYPTION_KEY) {
      this.logger.warn(
        '⚠️  CONFIG_ENCRYPTION_KEY non définie. Utilisation d\'une clé temporaire (NON RECOMMANDÉ EN PRODUCTION)',
      );
    }
  }

  /**
   * Chargement du cache au démarrage de l'application
   */
  async onModuleInit() {
    await this.loadCache();
  }

  /**
   * Chargement de toutes les configurations en cache
   */
  async loadCache(): Promise<void> {
    try {
      const configs = await this.configRepo.find();
      this.logger.log(`📦 Chargement de ${configs.length} configurations en cache`);

      for (const config of configs) {
        let value = config.valeur;

        // Déchiffrement si nécessaire
        if (config.type === 'encrypted' && value) {
          try {
            value = this.decrypt(value);
          } catch (error) {
            this.logger.error(
              `❌ Erreur de déchiffrement pour la clé '${config.cle}'`,
              error,
            );
            value = null;
          }
        }

        // Conversion de type
        const convertedValue = this.convertType(value, config.type);
        this.cache.set(config.cle, convertedValue);
      }

      this.logger.log('✅ Cache de configuration chargé avec succès');
    } catch (error) {
      this.logger.error('❌ Erreur lors du chargement du cache', error);
      throw error;
    }
  }

  /**
   * Récupération d'une valeur de configuration
   * @param key Clé de configuration
   * @param defaultValue Valeur par défaut si la clé n'existe pas
   * @returns Valeur de configuration
   */
  get<T = any>(key: string, defaultValue?: T): T {
    if (this.cache.has(key)) {
      return this.cache.get(key) as T;
    }

    if (defaultValue !== undefined) {
      return defaultValue;
    }

    this.logger.warn(`⚠️  Configuration '${key}' non trouvée et aucune valeur par défaut fournie`);
    return undefined as T;
  }

  /**
   * Mise à jour d'une valeur de configuration
   * @param key Clé de configuration
   * @param value Nouvelle valeur
   * @param userId ID de l'utilisateur qui modifie
   */
  async set(key: string, value: any, userId?: string): Promise<void> {
    const config = await this.configRepo.findOne({ where: { cle: key } });

    if (!config) {
      throw new Error(`Configuration '${key}' non trouvée`);
    }

    let valueToStore = value !== null && value !== undefined ? String(value) : null;

    // Chiffrement si nécessaire
    if (config.type === 'encrypted' && valueToStore) {
      valueToStore = this.encrypt(valueToStore);
    }

    config.valeur = valueToStore;
    config.modifieLe = new Date();

    if (userId) {
      config.modifiePar = { id: userId } as any;
    }

    await this.configRepo.save(config);

    // Mise à jour du cache (avec la valeur déchiffrée)
    this.cache.set(key, value);

    this.logger.log(`✅ Configuration '${key}' mise à jour`);
  }

  /**
   * Récupération de toutes les configurations par catégorie
   * @param category Catégorie de configuration
   * @returns Objet clé/valeur des configurations
   */
  async getByCategory(category: string): Promise<Record<string, any>> {
    const configs = await this.configRepo.find({
      where: { categorie: category as any },
    });

    const result: Record<string, any> = {};

    for (const config of configs) {
      let value = config.valeur;

      // Masquer les mots de passe
      if (config.type === 'encrypted') {
        value = value ? '***********' : null;
      } else {
        value = this.convertType(value, config.type);
      }

      result[config.cle] = {
        value,
        type: config.type,
        description: config.description,
      };
    }

    return result;
  }

  /**
   * Récupération de toutes les configurations (pour l'admin)
   * @returns Liste de toutes les configurations
   */
  async getAll(): Promise<Configuration[]> {
    const configs = await this.configRepo.find({
      order: { categorie: 'ASC', cle: 'ASC' },
    });

    // Masquer les valeurs chiffrées
    return configs.map((config) => ({
      ...config,
      valeur: config.type === 'encrypted' && config.valeur ? '***********' : config.valeur,
    }));
  }

  /**
   * Test de la configuration SMTP
   * @param testEmail Email de destination pour le test
   * @returns Objet avec success et error éventuel
   */
  async testSmtpConnection(testEmail?: string): Promise<{ success: boolean; error?: string }> {
    try {
      this.logger.log('🧪 Test de connexion SMTP...');

      // Récupération de la configuration SMTP
      const smtpHost = this.get<string>('smtp_host');
      const smtpPort = this.get<number>('smtp_port');
      const smtpSecure = this.get<boolean>('smtp_secure');
      const smtpAuthRequired = this.get<boolean>('smtp_auth_required');
      const smtpUser = this.get<string>('smtp_user');
      const smtpPassword = this.get<string>('smtp_password');
      const emailFromName = this.get<string>('email_from_name');
      const emailFromAddress = this.get<string>('email_from_address');

      // Import dynamique de nodemailer
      const nodemailer = await import('nodemailer');

      // Configuration du transporteur
      const transportConfig: any = {
        host: smtpHost,
        port: smtpPort,
        secure: smtpSecure,
      };

      if (smtpAuthRequired && smtpUser && smtpPassword) {
        transportConfig.auth = {
          user: smtpUser,
          pass: smtpPassword,
        };
      }

      const transporter = nodemailer.createTransport(transportConfig);

      // Vérification de la connexion
      await transporter.verify();
      this.logger.log('✅ Connexion SMTP vérifiée');

      // Si un email de test est fourni, on envoie un email
      if (testEmail) {
        await transporter.sendMail({
          from: `"${emailFromName}" <${emailFromAddress}>`,
          to: testEmail,
          subject: '🧪 Test de configuration SMTP - P\'titsPas',
          text: 'Ceci est un email de test pour vérifier la configuration SMTP de votre application P\'titsPas.',
          html: `
            <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
              <h2 style="color: #4CAF50;">✅ Test de configuration SMTP réussi !</h2>
              <p>Ceci est un email de test pour vérifier la configuration SMTP de votre application <strong>P'titsPas</strong>.</p>
              <p>Si vous recevez cet email, cela signifie que votre configuration SMTP fonctionne correctement.</p>
              <hr style="border: 1px solid #eee; margin: 20px 0;">
              <p style="color: #666; font-size: 12px;">
                Cet email a été envoyé automatiquement depuis votre application P'titsPas.<br>
                Configuration testée le ${new Date().toLocaleString('fr-FR')}
              </p>
            </div>
          `,
        });
        this.logger.log(`📧 Email de test envoyé à ${testEmail}`);
      }

      return { success: true };
    } catch (error) {
      this.logger.error('❌ Échec du test SMTP', error);
      return {
        success: false,
        error: error.message || 'Erreur inconnue lors du test SMTP',
      };
    }
  }

  /**
   * Vérification si la configuration initiale est terminée
   * @returns true si la configuration est terminée
   */
  isSetupCompleted(): boolean {
    return this.get<boolean>('setup_completed', false);
  }

  /**
   * Marquer la configuration initiale comme terminée
   * @param userId ID de l'utilisateur qui termine la configuration (null si non authentifié)
   */
  async markSetupCompleted(userId: string | null): Promise<void> {
    await this.set('setup_completed', 'true', userId ?? undefined);
    this.logger.log('✅ Configuration initiale marquée comme terminée');
  }

  /**
   * Conversion de type selon le type de configuration
   * @param value Valeur à convertir
   * @param type Type cible
   * @returns Valeur convertie
   */
  private convertType(value: string | null, type: string): any {
    if (value === null || value === undefined) {
      return null;
    }

    switch (type) {
      case 'number':
        return parseFloat(value);
      case 'boolean':
        return value === 'true' || value === '1';
      case 'json':
        try {
          return JSON.parse(value);
        } catch {
          return null;
        }
      case 'string':
      case 'encrypted':
      default:
        return value;
    }
  }

  /**
   * Chiffrement AES-256-CBC
   * @param text Texte à chiffrer
   * @returns Texte chiffré (format: iv:encrypted)
   */
  private encrypt(text: string): string {
    const iv = crypto.randomBytes(this.IV_LENGTH);
    const key = Buffer.from(this.ENCRYPTION_KEY, 'hex');
    const cipher = crypto.createCipheriv(this.ENCRYPTION_ALGORITHM, key, iv);

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

    // Format: iv:encrypted
    return `${iv.toString('hex')}:${encrypted}`;
  }

  /**
   * Déchiffrement AES-256-CBC
   * @param encryptedText Texte chiffré (format: iv:encrypted)
   * @returns Texte déchiffré
   */
  private decrypt(encryptedText: string): string {
    const parts = encryptedText.split(':');
    if (parts.length !== 2) {
      throw new Error('Format de chiffrement invalide');
    }

    const iv = Buffer.from(parts[0], 'hex');
    const encrypted = parts[1];
    const key = Buffer.from(this.ENCRYPTION_KEY, 'hex');

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

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

    return decrypted;
  }
}

