import { chromium } from 'playwright';
import { writeFileSync, unlinkSync, mkdirSync, rmdirSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { randomUUID } from 'crypto';
import type { Pool } from 'pg';
import type { OpinionCumplimiento } from '@horux/shared';
import { getDecryptedFiel } from './fiel.service.js';
import { getDecryptedFielContribuyente } from './contribuyente-fiel.service.js';
import { loginToSatOpinion } from './sat/sat-opinion-login.js';
import { extractOpinionPdf } from './sat/sat-opinion-scraper.js';
import { parseOpinionPdf } from './sat/sat-opinion-parser.js';
import { prisma, tenantDb } from '../config/database.js';

const PROCESS_TIMEOUT = 180_000; // 3 minutes per tenant

/**
 * Downloads and stores the Opinión de Cumplimiento for a tenant.
 */
export async function consultarOpinion(tenantId: string): Promise<OpinionCumplimiento> {
  const fiel = await getDecryptedFiel(tenantId);
  if (!fiel) {
    throw new Error('No hay FIEL configurada o está vencida');
  }

  const tempId = randomUUID();
  const tempDir = join(tmpdir(), `horux-fiel-${tempId}`);
  mkdirSync(tempDir, { recursive: true, mode: 0o700 });

  const cerPath = join(tempDir, 'cert.cer');
  const keyPath = join(tempDir, 'key.key');

  try {
    writeFileSync(cerPath, Buffer.from(fiel.cerContent, 'binary'), { mode: 0o600 });
    writeFileSync(keyPath, Buffer.from(fiel.keyContent, 'binary'), { mode: 0o600 });

    const browser = await chromium.launch({ headless: true });

    try {
      const context = await browser.newContext({
        viewport: { width: 1280, height: 720 },
      });
      const page = await context.newPage();

      const timeoutPromise = new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error('Timeout: proceso de opinión excedió 3 minutos')), PROCESS_TIMEOUT)
      );

      const resultPromise = (async () => {
        const reportPage = await loginToSatOpinion(page, cerPath, keyPath, fiel.password);
        const pdfBuffer = await extractOpinionPdf(reportPage);
        const parsed = await parseOpinionPdf(pdfBuffer);

        const tenant = await prisma.tenant.findUnique({
          where: { id: tenantId },
          select: { databaseName: true },
        });
        if (!tenant) throw new Error('Tenant no encontrado');

        const pool = await tenantDb.getPool(tenantId, tenant.databaseName);

        const { rows } = await pool.query(`
          INSERT INTO opiniones_cumplimiento (rfc, razon_social, estatus, folio, cadena_original, fecha_consulta, pdf)
          VALUES ($1, $2, $3, $4, $5, NOW(), $6)
          RETURNING id, rfc, razon_social as "razonSocial", estatus, folio, cadena_original as "cadenaOriginal",
                    fecha_consulta as "fechaConsulta", created_at as "createdAt"
        `, [parsed.rfc, parsed.razonSocial, parsed.estatus, parsed.folio, parsed.cadenaOriginal, pdfBuffer]);

        return rows[0] as OpinionCumplimiento;
      })();

      return await Promise.race([resultPromise, timeoutPromise]);
    } finally {
      await browser.close();
    }
  } finally {
    try { unlinkSync(cerPath); } catch { /* ok */ }
    try { unlinkSync(keyPath); } catch { /* ok */ }
    try { rmdirSync(tempDir); } catch { /* ok */ }
  }
}

/**
 * Get last N opinions for a tenant (metadata only, no PDF).
 */
export async function getOpiniones(pool: Pool, limit = 5, rfc?: string): Promise<OpinionCumplimiento[]> {
  const params: unknown[] = [limit];
  let rfcFilter = '';
  if (rfc) {
    rfcFilter = 'WHERE rfc = $2';
    params.push(rfc);
  }
  const { rows } = await pool.query(`
    SELECT id, rfc, razon_social as "razonSocial", estatus, folio,
           cadena_original as "cadenaOriginal",
           fecha_consulta as "fechaConsulta", created_at as "createdAt"
    FROM opiniones_cumplimiento
    ${rfcFilter}
    ORDER BY fecha_consulta DESC
    LIMIT $1
  `, params);
  return rows;
}

/**
 * Get PDF binary for a specific opinion.
 */
export async function getOpinionPdf(pool: Pool, id: number): Promise<Buffer | null> {
  const { rows } = await pool.query(
    `SELECT pdf FROM opiniones_cumplimiento WHERE id = $1`,
    [id]
  );
  return rows.length > 0 ? rows[0].pdf : null;
}

/**
 * Delete opinions older than 6 months.
 */
export async function limpiarOpinionesAntiguas(pool: Pool): Promise<number> {
  const { rowCount } = await pool.query(
    `DELETE FROM opiniones_cumplimiento WHERE fecha_consulta < NOW() - interval '6 months'`
  );
  return rowCount ?? 0;
}

/**
 * Downloads and stores the Opinión de Cumplimiento for a specific contribuyente
 * (despacho mode). Uses FIEL stored in the tenant BD instead of the central BD.
 */
export async function consultarOpinionContribuyente(
  pool: Pool,
  contribuyenteId: string,
): Promise<OpinionCumplimiento> {
  const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, '');
  const fiel = await getDecryptedFielContribuyente(pool, safeId);
  if (!fiel) {
    throw new Error('No hay FIEL configurada para este contribuyente o está vencida');
  }

  const tempId = randomUUID();
  const tempDir = join(tmpdir(), `horux-fiel-${tempId}`);
  mkdirSync(tempDir, { recursive: true, mode: 0o700 });

  const cerPath = join(tempDir, 'cert.cer');
  const keyPath = join(tempDir, 'key.key');

  try {
    writeFileSync(cerPath, Buffer.from(fiel.cerContent, 'binary'), { mode: 0o600 });
    writeFileSync(keyPath, Buffer.from(fiel.keyContent, 'binary'), { mode: 0o600 });

    const browser = await chromium.launch({ headless: true });

    try {
      const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
      const page = await context.newPage();

      const timeoutPromise = new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error('Timeout: proceso de opinión excedió 3 minutos')), PROCESS_TIMEOUT)
      );

      const resultPromise = (async () => {
        const reportPage = await loginToSatOpinion(page, cerPath, keyPath, fiel.password);
        const pdfBuffer = await extractOpinionPdf(reportPage);
        const parsed = await parseOpinionPdf(pdfBuffer);

        const { rows } = await pool.query(`
          INSERT INTO opiniones_cumplimiento (rfc, razon_social, estatus, folio, cadena_original, fecha_consulta, pdf)
          VALUES ($1, $2, $3, $4, $5, NOW(), $6)
          RETURNING id, rfc, razon_social as "razonSocial", estatus, folio, cadena_original as "cadenaOriginal",
                    fecha_consulta as "fechaConsulta", created_at as "createdAt"
        `, [parsed.rfc, parsed.razonSocial, parsed.estatus, parsed.folio, parsed.cadenaOriginal, pdfBuffer]);

        return rows[0] as OpinionCumplimiento;
      })();

      return await Promise.race([resultPromise, timeoutPromise]);
    } finally {
      await browser.close();
    }
  } finally {
    try { unlinkSync(cerPath); } catch { /* ok */ }
    try { unlinkSync(keyPath); } catch { /* ok */ }
    try { rmdirSync(tempDir); } catch { /* ok */ }
  }
}
