import type { Pool } from 'pg';
import { OBLIGACIONES_CATALOGO } from '../constants/obligaciones-fiscales.js';

export interface EvidenciaRow {
  id: number;
  obligacionId: string;
  periodo: string;
  contribuyenteId: string;
  tipoDocumento: 'declaracion' | 'pago' | 'acuse' | 'complemento';
  archivo: Buffer;
  archivoFilename: string;
  archivoMime: string;
  notas: string | null;
  subidoPor: string | null;
  subidoPorEmail: string | null;
  createdAt: string;
}

export interface CreateEvidenciaInput {
  obligacionId: string;
  periodo: string;
  contribuyenteId: string;
  tipoDocumento: 'declaracion' | 'pago' | 'acuse' | 'complemento';
  pdfBase64: string;
  pdfFilename: string;
  notas?: string;
  subidoPor: string; // userId UUID
  subidoPorEmail?: string;
}

function rowToEvidencia(r: any): EvidenciaRow {
  return {
    id: r.id,
    obligacionId: r.obligacion_id,
    periodo: r.periodo,
    contribuyenteId: r.contribuyente_id,
    tipoDocumento: r.tipo_documento,
    archivo: Buffer.from(r.archivo),
    archivoFilename: r.archivo_filename,
    archivoMime: r.archivo_mime,
    notas: r.notas,
    subidoPor: r.subido_por,
    subidoPorEmail: r.subido_por_email,
    createdAt: r.created_at.toISOString(),
  };
}

async function getObligacionContribuyente(pool: Pool, obligacionId: string): Promise<{ contribuyenteId: string; catalogoId: string | null } | null> {
  const { rows } = await pool.query<{ contribuyente_id: string; catalogo_id: string | null }>(
    `SELECT contribuyente_id, catalogo_id FROM obligaciones_contribuyente WHERE id = $1`,
    [obligacionId],
  );
  const row = rows[0];
  if (!row) return null;
  return { contribuyenteId: row.contribuyente_id, catalogoId: row.catalogo_id };
}

function requierePago(obligacion: { catalogoId: string | null }): boolean {
  if (!obligacion.catalogoId) return true; // conservador: sin catálogo, requiere pago
  const catalogo = OBLIGACIONES_CATALOGO.find((o) => o.id === obligacion.catalogoId);
  return catalogo?.requierePago ?? true;
}

function esDocumentoDeclaracion(tipo: string): boolean {
  return tipo === 'declaracion' || tipo === 'acuse' || tipo === 'complemento';
}

async function updatePeriodoStatus(
  pool: Pool,
  obligacionId: string,
  periodo: string,
  tipoDocumento: string,
  reqPago: boolean,
  completadaPor: string,
  notas?: string,
): Promise<{ completada: boolean; declaracionPresentada: boolean; pagoPresentado: boolean }> {
  const { rows } = await pool.query<{
    declaracion_presentada: boolean;
    pago_presentado: boolean;
    completada: boolean;
  }>(
    `SELECT declaracion_presentada, pago_presentado, completada
     FROM obligacion_periodos
     WHERE obligacion_id = $1 AND periodo = $2`,
    [obligacionId, periodo],
  );

  const existing = rows[0];
  let declaracionPresentada = existing?.declaracion_presentada ?? false;
  let pagoPresentado = existing?.pago_presentado ?? false;

  if (esDocumentoDeclaracion(tipoDocumento)) declaracionPresentada = true;
  if (tipoDocumento === 'pago') pagoPresentado = true;

  const completada = !reqPago || pagoPresentado;
  const now = new Date();

  if (existing) {
    await pool.query(
      `UPDATE obligacion_periodos
       SET declaracion_presentada = $3,
           pago_presentado = $4,
           completada = $5,
           completada_at = CASE WHEN $5 THEN COALESCE(completada_at, $6) ELSE completada_at END,
           completada_por = CASE WHEN $5 THEN COALESCE(completada_por, $7) ELSE completada_por END,
           notas = COALESCE($8, notas)
       WHERE obligacion_id = $1 AND periodo = $2`,
      [obligacionId, periodo, declaracionPresentada, pagoPresentado, completada, now, completadaPor, notas ?? null],
    );
  } else {
    await pool.query(
      `INSERT INTO obligacion_periodos
        (obligacion_id, periodo, declaracion_presentada, pago_presentado, completada, completada_at, completada_por, notas)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
      [obligacionId, periodo, declaracionPresentada, pagoPresentado, completada, completada ? now : null, completada ? completadaPor : null, notas ?? null],
    );
  }

  if (completada) {
    await pool.query(
      `UPDATE alertas SET resuelta = true WHERE tipo = $1 AND resuelta = false`,
      [`ob-${obligacionId}-${periodo}`],
    );
  }

  return { completada, declaracionPresentada, pagoPresentado };
}

async function recalcPeriodoStatus(
  pool: Pool,
  obligacionId: string,
  periodo: string,
  reqPago: boolean,
): Promise<void> {
  const { rows } = await pool.query<{ tipo_documento: string }>(
    `SELECT tipo_documento FROM obligacion_evidencias WHERE obligacion_id = $1 AND periodo = $2`,
    [obligacionId, periodo],
  );

  const declaracionPresentada = rows.some((r) => esDocumentoDeclaracion(r.tipo_documento));
  const pagoPresentado = rows.some((r) => r.tipo_documento === 'pago');
  const completada = !reqPago || pagoPresentado;

  await pool.query(
    `UPDATE obligacion_periodos
     SET declaracion_presentada = $3,
         pago_presentado = $4,
         completada = $5,
         completada_at = CASE WHEN $5 THEN COALESCE(completada_at, NOW()) ELSE completada_at END
     WHERE obligacion_id = $1 AND periodo = $2`,
    [obligacionId, periodo, declaracionPresentada, pagoPresentado, completada],
  );
}

export async function createEvidencia(
  pool: Pool,
  data: CreateEvidenciaInput,
): Promise<{ evidencia: EvidenciaRow; completada: boolean; declaracionPresentada: boolean; pagoPresentado: boolean }> {
  const obligacion = await getObligacionContribuyente(pool, data.obligacionId);
  if (!obligacion) throw new Error('Obligación no encontrada');
  if (obligacion.contribuyenteId !== data.contribuyenteId) throw new Error('La obligación no pertenece al contribuyente');

  const reqPago = requierePago(obligacion);
  const archivo = Buffer.from(data.pdfBase64, 'base64');

  const { rows } = await pool.query(
    `INSERT INTO obligacion_evidencias
      (obligacion_id, periodo, contribuyente_id, tipo_documento, archivo, archivo_filename, archivo_mime, notas, subido_por, subido_por_email)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
     RETURNING id, obligacion_id, periodo, contribuyente_id, tipo_documento, archivo, archivo_filename, archivo_mime,
               notas, subido_por, subido_por_email, created_at`,
    [data.obligacionId, data.periodo, data.contribuyenteId, data.tipoDocumento, archivo, data.pdfFilename, 'application/pdf', data.notas ?? null, data.subidoPor, data.subidoPorEmail],
  );

  const status = await updatePeriodoStatus(
    pool,
    data.obligacionId,
    data.periodo,
    data.tipoDocumento,
    reqPago,
    data.subidoPor,
    data.notas,
  );

  return { evidencia: rowToEvidencia(rows[0]), ...status };
}

export async function listEvidencias(
  pool: Pool,
  contribuyenteId: string,
  filters?: { periodo?: string; obligacionId?: string },
): Promise<EvidenciaRow[]> {
  const conditions: string[] = ['contribuyente_id = $1'];
  const params: unknown[] = [contribuyenteId];

  if (filters?.periodo) {
    params.push(filters.periodo);
    conditions.push(`periodo = $${params.length}`);
  }
  if (filters?.obligacionId) {
    params.push(filters.obligacionId);
    conditions.push(`obligacion_id = $${params.length}`);
  }

  const { rows } = await pool.query(
    `SELECT id, obligacion_id, periodo, contribuyente_id, tipo_documento, archivo, archivo_filename, archivo_mime,
            notas, subido_por, subido_por_email, created_at
     FROM obligacion_evidencias
     WHERE ${conditions.join(' AND ')}
     ORDER BY created_at DESC`,
    params,
  );
  return rows.map(rowToEvidencia);
}

export async function getEvidenciaPdf(
  pool: Pool,
  id: number,
): Promise<{ buffer: Buffer; filename: string; mime: string } | null> {
  const { rows } = await pool.query(
    `SELECT archivo, archivo_filename, archivo_mime FROM obligacion_evidencias WHERE id = $1`,
    [id],
  );
  if (rows.length === 0 || !rows[0].archivo) return null;
  return {
    buffer: Buffer.from(rows[0].archivo),
    filename: rows[0].archivo_filename || `evidencia-${id}.pdf`,
    mime: rows[0].archivo_mime || 'application/pdf',
  };
}

export async function deleteEvidencia(
  pool: Pool,
  id: number,
): Promise<{ obligacionId: string; periodo: string } | null> {
  const { rows } = await pool.query<{ obligacion_id: string; periodo: string }>(
    `DELETE FROM obligacion_evidencias WHERE id = $1 RETURNING obligacion_id, periodo`,
    [id],
  );
  if (rows.length === 0) return null;

  const { obligacion_id: obligacionId, periodo } = rows[0];
  const obligacion = await getObligacionContribuyente(pool, obligacionId);
  if (obligacion) {
    const reqPago = requierePago(obligacion);
    await recalcPeriodoStatus(pool, obligacionId, periodo, reqPago);
  }
  return { obligacionId, periodo };
}

export async function getPeriodoStatus(
  pool: Pool,
  obligacionId: string,
  periodo: string,
): Promise<{ completada: boolean; declaracionPresentada: boolean; pagoPresentado: boolean } | null> {
  const { rows } = await pool.query<{
    completada: boolean;
    declaracion_presentada: boolean;
    pago_presentado: boolean;
  }>(
    `SELECT completada, declaracion_presentada, pago_presentado
     FROM obligacion_periodos
     WHERE obligacion_id = $1 AND periodo = $2`,
    [obligacionId, periodo],
  );
  if (rows.length === 0) return null;
  return {
    completada: rows[0].completada,
    declaracionPresentada: rows[0].declaracion_presentada,
    pagoPresentado: rows[0].pago_presentado,
  };
}
