import { Credential } from '@nodecfdi/credentials/node';
import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { prisma } from '../config/database.js';
import { env } from '../config/env.js';
import { encryptFielCredentials, encrypt, decryptFielCredentials } from './sat/sat-crypto.service.js';
import { emailService } from './email/email.service.js';
import type { FielStatus } from '@horux/shared';

/**
 * Sube y valida credenciales FIEL
 */
export async function uploadFiel(
  tenantId: string,
  cerBase64: string,
  keyBase64: string,
  password: string
): Promise<{ success: boolean; message: string; status?: FielStatus }> {
  try {
    // Decodificar archivos de Base64
    const cerData = Buffer.from(cerBase64, 'base64');
    const keyData = Buffer.from(keyBase64, 'base64');
    
    // Validar que los archivos sean válidos y coincidan
    let credential: Credential;
    try {
      credential = Credential.create(
        cerData.toString('binary'),
        keyData.toString('binary'),
        password
      );
    } catch (error: any) {
      return {
        success: false,
        message: 'Los archivos de la FIEL no son válidos o la contraseña es incorrecta',
      };
    }
    
    // Verificar que sea una FIEL (no CSD)
    if (!credential.isFiel()) {
      return {
        success: false,
        message: 'El certificado proporcionado no es una FIEL (e.firma). Parece ser un CSD.',
      };
    }
    
    // Obtener información del certificado
    const certificate = credential.certificate();
    const rfc = certificate.rfc();
    const serialNumber = certificate.serialNumber().bytes();
    // validFromDateTime() y validToDateTime() retornan strings ISO o objetos DateTime
    const validFromRaw = certificate.validFromDateTime();
    const validUntilRaw = certificate.validToDateTime();
    const validFrom = new Date(String(validFromRaw));
    const validUntil = new Date(String(validUntilRaw));

    // Verificar que no esté vencida
    if (new Date() > validUntil) {
      return {
        success: false,
        message: 'La FIEL está vencida desde ' + validUntil.toLocaleDateString(),
      };
    }
    
    // Encriptar credenciales (per-component IV/tag)
    const {
      encryptedCer,
      encryptedKey,
      encryptedPassword,
      cerIv,
      cerTag,
      keyIv,
      keyTag,
      passwordIv,
      passwordTag,
    } = encryptFielCredentials(cerData, keyData, password);

    // Detectar si es la primera subida (no existe fielCredential previo activo)
    // — se usa abajo para disparar Opinión de Cumplimiento + CSF iniciales.
    const existingFiel = await prisma.fielCredential.findUnique({
      where: { tenantId },
      select: { isActive: true },
    });
    const esPrimeraSubida = !existingFiel || !existingFiel.isActive;

    // Guardar o actualizar en BD
    await prisma.fielCredential.upsert({
      where: { tenantId },
      create: {
        tenantId,
        rfc,
        cerData: encryptedCer,
        keyData: encryptedKey,
        keyPasswordEncrypted: encryptedPassword,
        cerIv,
        cerTag,
        keyIv,
        keyTag,
        passwordIv,
        passwordTag,
        serialNumber,
        validFrom,
        validUntil,
        isActive: true,
      },
      update: {
        rfc,
        cerData: encryptedCer,
        keyData: encryptedKey,
        keyPasswordEncrypted: encryptedPassword,
        cerIv,
        cerTag,
        keyIv,
        keyTag,
        passwordIv,
        passwordTag,
        serialNumber,
        validFrom,
        validUntil,
        isActive: true,
        updatedAt: new Date(),
      },
    });
    
    // Save encrypted files to filesystem (dual storage)
    try {
      const fielDir = join(env.FIEL_STORAGE_PATH, rfc.toUpperCase());
      await mkdir(fielDir, { recursive: true, mode: 0o700 });

      // Re-encrypt for filesystem (independent keys from DB)
      const fsEncrypted = encryptFielCredentials(cerData, keyData, password);

      await writeFile(join(fielDir, 'certificate.cer.enc'), fsEncrypted.encryptedCer, { mode: 0o600 });
      await writeFile(join(fielDir, 'certificate.cer.iv'), fsEncrypted.cerIv, { mode: 0o600 });
      await writeFile(join(fielDir, 'certificate.cer.tag'), fsEncrypted.cerTag, { mode: 0o600 });
      await writeFile(join(fielDir, 'private_key.key.enc'), fsEncrypted.encryptedKey, { mode: 0o600 });
      await writeFile(join(fielDir, 'private_key.key.iv'), fsEncrypted.keyIv, { mode: 0o600 });
      await writeFile(join(fielDir, 'private_key.key.tag'), fsEncrypted.keyTag, { mode: 0o600 });

      // Encrypt and store metadata
      const metadata = JSON.stringify({
        serial: serialNumber,
        validFrom: validFrom.toISOString(),
        validUntil: validUntil.toISOString(),
        uploadedAt: new Date().toISOString(),
        rfc: rfc.toUpperCase(),
      });
      const metaEncrypted = encrypt(Buffer.from(metadata, 'utf-8'));
      await writeFile(join(fielDir, 'metadata.json.enc'), metaEncrypted.encrypted, { mode: 0o600 });
      await writeFile(join(fielDir, 'metadata.json.iv'), metaEncrypted.iv, { mode: 0o600 });
      await writeFile(join(fielDir, 'metadata.json.tag'), metaEncrypted.tag, { mode: 0o600 });
    } catch (fsError) {
      console.error('[FIEL] Filesystem storage failed (DB storage OK):', fsError);
    }

    // Notify admin that client uploaded FIEL
    const tenant = await prisma.tenant.findUnique({
      where: { id: tenantId },
      select: { nombre: true, rfc: true },
    });
    if (tenant) {
      emailService.sendFielNotification({
        clienteNombre: tenant.nombre,
        clienteRfc: tenant.rfc,
      }).catch(err => console.error('[EMAIL] FIEL notification failed:', err));
    }

    // Al primer upload de FIEL, disparar Opinión de Cumplimiento + CSF en
    // background. Fire-and-forget — no bloqueamos la respuesta porque ambos
    // procesos abren Playwright y tardan minutos. La CSF además autocompleta
    // domicilio y regímenes activos del tenant.
    if (esPrimeraSubida) {
      import('./opinion-cumplimiento.service.js').then(({ consultarOpinion }) =>
        consultarOpinion(tenantId),
      ).catch(err => console.error(`[FIEL first-upload] Opinión falló para tenant ${tenantId}:`, err.message || err));

      import('./constancia.service.js').then(({ consultarConstancia }) =>
        consultarConstancia(tenantId),
      ).catch(err => console.error(`[FIEL first-upload] CSF falló para tenant ${tenantId}:`, err.message || err));
    }

    const daysUntilExpiration = Math.ceil(
      (validUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
    );

    return {
      success: true,
      message: 'FIEL configurada correctamente',
      status: {
        configured: true,
        rfc,
        serialNumber,
        validFrom: validFrom.toISOString(),
        validUntil: validUntil.toISOString(),
        isExpired: false,
        daysUntilExpiration,
      },
    };
  } catch (error: any) {
    console.error('[FIEL Upload Error]', error);
    return {
      success: false,
      message: error.message || 'Error al procesar la FIEL',
    };
  }
}

/**
 * Obtiene el estado de la FIEL de un tenant
 */
export async function getFielStatus(tenantId: string): Promise<FielStatus> {
  const fiel = await prisma.fielCredential.findUnique({
    where: { tenantId },
    select: {
      rfc: true,
      serialNumber: true,
      validFrom: true,
      validUntil: true,
      isActive: true,
    },
  });
  
  if (!fiel || !fiel.isActive) {
    return { configured: false };
  }
  
  const now = new Date();
  const isExpired = now > fiel.validUntil;
  const daysUntilExpiration = Math.ceil(
    (fiel.validUntil.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
  );
  
  return {
    configured: true,
    rfc: fiel.rfc,
    serialNumber: fiel.serialNumber || undefined,
    validFrom: fiel.validFrom.toISOString(),
    validUntil: fiel.validUntil.toISOString(),
    isExpired,
    daysUntilExpiration: isExpired ? 0 : daysUntilExpiration,
  };
}

/**
 * Elimina la FIEL de un tenant
 */
export async function deleteFiel(tenantId: string): Promise<boolean> {
  try {
    await prisma.fielCredential.delete({
      where: { tenantId },
    });
    return true;
  } catch {
    return false;
  }
}

/**
 * Obtiene las credenciales desencriptadas para usar en sincronización
 * Solo debe usarse internamente por el servicio de SAT
 */
export async function getDecryptedFiel(tenantId: string): Promise<{
  cerContent: string;
  keyContent: string;
  password: string;
  rfc: string;
} | null> {
  const fiel = await prisma.fielCredential.findUnique({
    where: { tenantId },
  });

  if (!fiel || !fiel.isActive) {
    return null;
  }

  // Verificar que no esté vencida
  if (new Date() > fiel.validUntil) {
    return null;
  }

  try {
    // Desencriptar credenciales (per-component IV/tag)
    const { cerData, keyData, password } = decryptFielCredentials(
      Buffer.from(fiel.cerData),
      Buffer.from(fiel.keyData),
      Buffer.from(fiel.keyPasswordEncrypted),
      Buffer.from(fiel.cerIv),
      Buffer.from(fiel.cerTag),
      Buffer.from(fiel.keyIv),
      Buffer.from(fiel.keyTag),
      Buffer.from(fiel.passwordIv),
      Buffer.from(fiel.passwordTag)
    );

    return {
      cerContent: cerData.toString('binary'),
      keyContent: keyData.toString('binary'),
      password,
      rfc: fiel.rfc,
    };
  } catch (error) {
    console.error('[FIEL Decrypt Error]', error);
    return null;
  }
}

/**
 * Verifica si un tenant tiene FIEL configurada y válida
 */
export async function hasFielConfigured(tenantId: string): Promise<boolean> {
  const status = await getFielStatus(tenantId);
  return status.configured && !status.isExpired;
}
