<?php

declare(strict_types=1);

namespace App\Services;

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
use AppConfig;
use App\Services\LogService;

require_once __DIR__ . '/EmailTemplates.php';
require_once __DIR__ . '/PasswordSecurityService.php';

class ApiService {

  /**
   * Envoie un email selon un type prédéfini
   *
   * @param string $email Email du destinataire
   * @param string $name Nom du destinataire
   * @param string $type Type d'email ('welcome', 'lostpwd', etc.)
   * @param array $data Données supplémentaires pour le template
   * @return int 1 si succès, 0 si échec
   */
  public static function sendEmail(string $email, string $name, string $type, array $data = []): int {
    require_once __DIR__ . '/../../vendor/autoload.php';

    $name = ucwords($name);
    try {
      $mail = new PHPMailer(true);

      // Récupération des paramètres SMTP depuis la configuration
      $appConfig = AppConfig::getInstance();
      $smtpConfig = $appConfig->getSmtpConfig();
      $emailConfig = $appConfig->getEmailConfig();

      // Configuration du serveur
      $mail->isSMTP();
      $mail->Host = $smtpConfig['host'];
      $mail->SMTPAuth = $smtpConfig['auth'];
      $mail->Username = $smtpConfig['user'];
      $mail->Password = $smtpConfig['pass'];
      $mail->SMTPSecure = $smtpConfig['secure'];
      $mail->Port = $smtpConfig['port'];

      // Configuration de base
      $mail->setFrom($emailConfig['from'], 'GEOSECTOR');
      $mail->addAddress($email, $name);
      $mail->isHTML(true);
      $mail->CharSet = 'UTF-8';

      // Configuration selon le type d'email
      switch ($type) {
        case 'welcome':
          $mail->Subject = 'Bienvenue sur GEOSECTOR';
          $mail->Body = EmailTemplates::getWelcomeTemplate($name, $data['username'] ?? '', $data['password']);
          break;
          
        case 'welcome_username':
          $mail->Subject = 'GEOSECTOR - Votre identifiant de connexion';
          $mail->Body = EmailTemplates::getWelcomeUsernameTemplate($name, $data['username'] ?? '');
          break;
          
        case 'welcome_password':
          $mail->Subject = 'GEOSECTOR - Votre mot de passe';
          $mail->Body = EmailTemplates::getWelcomePasswordTemplate($name, $data['password'] ?? '');
          break;

        case 'lostpwd':
          $mail->Subject = 'Réinitialisation de votre mot de passe GEOSECTOR';
          $mail->Body = EmailTemplates::getLostPasswordTemplate($name, $data['username'] ?? '', $data['password']);
          break;

        case 'password_reset':
          $mail->Subject = 'Réinitialisation de votre mot de passe GEOSECTOR';
          $mail->Body = EmailTemplates::getLostPasswordTemplate($name, $data['username'] ?? '', $data['password'] ?? '');
          break;

        case 'alert':
          $mail->Subject = $data['subject'] ?? 'Alerte GEOSECTOR';
          $mail->Body = EmailTemplates::getAlertTemplate($data['subject'] ?? 'Alerte', $data['message'] ?? '');
          break;

        case 'security_alert':
          $mail->Subject = $data['subject'] ?? 'Alerte de Sécurité GEOSECTOR';
          $mail->Body = $data['body'] ?? EmailTemplates::getAlertTemplate($data['subject'] ?? 'Alerte de Sécurité', $data['message'] ?? '');
          break;

        case 'receipt':
          $mail->Subject = 'Reçu de passage GEOSECTOR';
          $mail->Body = EmailTemplates::getReceiptTemplate(
            $name,
            $data['date'] ?? date('d/m/Y'),
            $data['address'] ?? '',
            $data['amount'] ?? '0',
            $data['paymentMethod'] ?? 'Espèces'
          );
          break;

        default:
          throw new Exception("Type d'email non reconnu");
      }

      $mail->send();
      LogService::log("Email '$type' envoyé avec succès", [
        'level' => 'info',
        'email' => $email,
        'type' => $type
      ]);

      return 1;
    } catch (Exception $e) {
      LogService::log("Échec d'envoi d'email", [
        'level' => 'error',
        'error' => $e->getMessage(),
        'email' => $email,
        'type' => $type
      ]);

      return 0;
    }
  }

  // Pour les données qui servent de clé de recherche (comme l'email)
  public static function encryptSearchableData(string $data): string {
    if (empty($data)) {
      return '';
    }

    // Forcer un padding cohérent en ajoutant un caractère spécial de contrôle
    $data = $data . "\x01";  // Garantit que même un bloc parfait reçoit du padding

    $keyBase64 = AppConfig::getInstance()->getEncryptionKey();
    $key = base64_decode($keyBase64); // Décoder la clé base64 en binaire
    $iv = str_repeat("\0", 16); // IV fixe

    // Expliciter les options de padding
    $options = 0;  // PKCS7 padding par défaut
    $encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, $options, $iv);

    return base64_encode($encrypted);
  }

  // Pour les données qui ne servent pas de clé de recherche (comme le nom)
  public static function encryptData(string $data): string {
    if (empty($data)) {
      return '';
    }

    $keyBase64 = AppConfig::getInstance()->getEncryptionKey();
    $key = base64_decode($keyBase64); // Décoder la clé base64 en binaire
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('AES-256-CBC'));
    $encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
    return base64_encode($iv . $encrypted);
  }

  public static function decryptSearchableData(string $encryptedData): string {
    if (empty($encryptedData)) {
      return '';
    }

    // Décoder la chaîne base64
    $encrypted = base64_decode($encryptedData);

    // Vérifier que le décodage base64 a fonctionné
    if ($encrypted === false) {
      return ''; // Échec du décodage, retourner une chaîne vide
    }

    // Méthode simple et robuste utilisant le même IV fixe que pour le chiffrement
    $keyBase64 = AppConfig::getInstance()->getEncryptionKey();
    $key = base64_decode($keyBase64); // Décoder la clé base64 en binaire
    $iv = str_repeat("\0", 16); // IV fixe identique à celui utilisé pour le chiffrement

    // Déchiffrer avec la méthode standard
    $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);

    // Si le déchiffrement a échoué, retourner une chaîne vide
    if ($decrypted === false) {
      return '';
    }

    // Supprimer uniquement le caractère de contrôle ajouté
    if (substr($decrypted, -1) === "\x01") {
      return substr($decrypted, 0, -1);
    }

    return $decrypted; // Pour la rétrocompatibilité avec les anciennes données
  }

  public static function decryptData(string $encryptedData): string {
    if (empty($encryptedData)) {
      return '';
    }

    $keyBase64 = AppConfig::getInstance()->getEncryptionKey();
    $key = base64_decode($keyBase64); // Décoder la clé base64 en binaire

    $data = base64_decode($encryptedData);

    // Vérifier que le décodage base64 a fonctionné
    if ($data === false) {
      return $encryptedData; // Retourner la chaîne d'origine en cas d'échec
    }

    $ivLength = openssl_cipher_iv_length('AES-256-CBC');

    // Vérifier que les données sont assez longues pour contenir l'IV
    if (strlen($data) <= $ivLength) {
      return $encryptedData; // Retourner la chaîne d'origine en cas d'échec
    }

    $iv = substr($data, 0, $ivLength);
    $encrypted = substr($data, $ivLength);

    $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);

    // Si le déchiffrement échoue, retourner la chaîne d'origine
    return $decrypted !== false ? $decrypted : $encryptedData;
  }

  /**
   * Génère un nom d'utilisateur unique à partir du nom, du code postal et de la ville
   * 
   * @param PDO $db Instance de la base de données
   * @param string $name Nom de l'utilisateur
   * @param string $postalCode Code postal
   * @param string $cityName Nom de la ville
   * @param int $minLength Longueur minimale du nom d'utilisateur (par défaut 10)
   * @return string Nom d'utilisateur généré
   */
  public static function generateUserName(\PDO $db, string $name, string $postalCode, string $cityName, int $minLength = 10): string {
    // Nettoyer et préparer les chaînes
    $name = preg_replace('/[^a-zA-Z0-9]/', '', strtolower($name));
    $postalCode = preg_replace('/[^0-9]/', '', $postalCode);
    $cityName = preg_replace('/[^a-zA-Z0-9]/', '', strtolower($cityName));

    // Extraire les premières lettres du nom (2 à 5 caractères)
    $nameLength = min(5, max(2, rand(2, strlen($name))));
    $namePart = substr($name, 0, $nameLength);

    // Extraire une partie du code postal (2 à 3 chiffres)
    $postalLength = min(3, max(2, rand(2, strlen($postalCode))));
    $postalPart = substr($postalCode, 0, $postalLength);

    // Extraire une partie du nom de la ville (2 à 4 caractères)
    $cityLength = min(4, max(2, rand(2, strlen($cityName))));
    $cityPart = substr($cityName, 0, $cityLength);

    // Combiner les parties avec un séparateur aléatoire
    $separators = ['', '.', '_', '-'];
    $separator1 = $separators[array_rand($separators)];
    $separator2 = $separators[array_rand($separators)];

    // Ajouter un nombre aléatoire pour garantir l'unicité
    $randomNum = rand(10, 999);

    // Construire le nom d'utilisateur
    $username = $namePart . $separator1 . $postalPart . $separator2 . $cityPart . $randomNum;

    // S'assurer que la longueur minimale est respectée
    while (strlen($username) < $minLength) {
      $username .= rand(0, 9);
    }

    // Vérifier l'unicité du nom d'utilisateur dans la base de données
    $isUnique = false;
    $attempts = 0;
    $originalUsername = $username;

    while (!$isUnique && $attempts < 10) {
      // Chiffrer le nom d'utilisateur pour la recherche
      $encryptedUsername = self::encryptSearchableData($username);

      // Vérifier si le nom d'utilisateur existe déjà
      $stmt = $db->prepare('SELECT COUNT(*) as count FROM users WHERE encrypted_user_name = ?');
      $stmt->execute([$encryptedUsername]);
      $result = $stmt->fetch(\PDO::FETCH_ASSOC);

      if ($result && $result['count'] == 0) {
        $isUnique = true;
      } else {
        // Ajouter un nombre aléatoire supplémentaire
        $username = $originalUsername . rand(100, 999);
        $attempts++;
      }
    }

    return $username;
  }

  /**
   * Génère un mot de passe sécurisé aléatoire non compromis
   * Utilise le service PasswordSecurityService pour vérifier contre HIBP
   * 
   * @param int $minLength Longueur minimale du mot de passe (par défaut 12)
   * @param int $maxLength Longueur maximale du mot de passe (par défaut 16)
   * @return string Mot de passe généré
   */
  public static function generateSecurePassword(int $minLength = 12, int $maxLength = 16): string {
    $length = random_int($minLength, $maxLength);
    
    // Utiliser le nouveau service pour générer un mot de passe non compromis
    $password = PasswordSecurityService::generateSecurePassword($length, 10);
    
    // Si le service échoue (très rare), utiliser l'ancienne méthode
    if ($password === null) {
      LogService::log('Fallback vers génération de mot de passe classique', [
        'level' => 'warning',
        'reason' => 'PasswordSecurityService a échoué'
      ]);
      
      $lowercase = 'abcdefghijklmnopqrstuvwxyz';
      $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      $numbers = '0123456789';
      $special = '!@#$%^&*()_+-=[]{}|;:,.<>?';

      $password = '';

      // Au moins un de chaque type
      $password .= $lowercase[random_int(0, strlen($lowercase) - 1)];
      $password .= $uppercase[random_int(0, strlen($uppercase) - 1)];
      $password .= $numbers[random_int(0, strlen($numbers) - 1)];
      $password .= $special[random_int(0, strlen($special) - 1)];

      // Compléter avec des caractères aléatoires
      $all = $lowercase . $uppercase . $numbers . $special;
      for ($i = strlen($password); $i < $length; $i++) {
        $password .= $all[random_int(0, strlen($all) - 1)];
      }

      // Mélanger le mot de passe
      return str_shuffle($password);
    }
    
    return $password;
  }
}
