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

/**
 * Keyword-based matching: each catalog entry has discriminant keywords
 * that must ALL appear in the SAT description (normalized, lowercase, no accents).
 * Multiple keyword sets per entry allow for variant phrasings.
 */
const CATALOG_MATCH_RULES: Array<{ id: string; keywords: string[][] }> = [
  // ISR provisionales
  { id: 'isr-provisional', keywords: [
    ['pago provisional', 'isr', 'actividades empresariales'],
    ['pago provisional', 'isr personas morales', 'general'],
    ['pago provisional mensual de isr personas morales'],
  ]},
  { id: 'isr-resico-pm', keywords: [
    ['isr', 'simplificado de confianza', 'pago provisional'],
    ['isr', 'simplificado de confianza', 'pago provisional mensual'],
  ]},
  { id: 'isr-resico-pf', keywords: [
    ['isr', 'simplificado de confianza', 'ajuste anual'],
    // Note: PF RESICO "pago provisional" is same id as PM; differentiate by RFC length at runtime
  ]},

  // IVA
  { id: 'iva-mensual', keywords: [
    ['pago definitivo', 'iva', 'mensual'],
    ['pago definitivo mensual de iva'],
  ]},

  // DIOT
  { id: 'diot', keywords: [
    ['proveedores', 'iva'],
    ['diot'],
  ]},

  // Anuales
  { id: 'anual-isr-pm', keywords: [
    ['declaracion anual', 'isr', 'personas morales'],
    ['anual de isr del regimen', 'simplificado', 'personas morales'],
  ]},
  { id: 'anual-isr-pf', keywords: [
    ['declaracion anual', 'isr', 'personas fisicas'],
    ['ajuste anual', 'isr', 'declaracion anual', 'simplificado'],
  ]},

  // Retenciones ISR
  { id: 'ret-isr-sueldos', keywords: [
    ['retenciones', 'isr', 'sueldos y salarios'],
    ['retenciones mensuales de isr por sueldos'],
  ]},
  { id: 'ret-isr-asimilados', keywords: [
    ['retenciones', 'isr', 'asimilados a salarios'],
    ['retenciones mensuales de isr por ingresos asimilados'],
  ]},
  { id: 'ret-isr-honorarios', keywords: [
    ['retencion', 'isr', 'servicios profesionales'],
    ['retenciones de isr por servicios profesionales'],  // TPR variant (missing accent)
  ]},

  // Retenciones IVA
  { id: 'ret-iva', keywords: [
    ['retenciones de iva'],
    ['retenciones', 'iva', 'mensual'],
  ]},

  // IEPS
  { id: 'ieps', keywords: [
    ['ieps'],
  ]},

  // RIF bimestral
  { id: 'isr-provisional', keywords: [
    ['bimestral del rif'],
    ['pago definitivo bimestral del rif'],
  ]},

  // Arrendamiento
  { id: 'isr-provisional', keywords: [
    ['isr', 'arrendamiento de inmuebles', 'pago provisional'],
    ['isr por arrendamiento de inmuebles pf'],
  ]},

  // Informativas (no tienen match directo en catálogo pero agrupar con DIM)
  { id: 'dim', keywords: [
    ['declaracion informativa anual', 'pagos y retenciones'],
    ['declaracion informativa anual de clientes y proveedores'],
    ['declaracion informativa anual de retenciones'],
    ['declaracion informativa de iva con la anual'],
  ]},
];

function normalizeForMatch(s: string): string {
  return s
    .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/[.,;:()]/g, '')
    .replace(/\s+/g, ' ')
    .trim();
}

function matchCsfToCatalog(descripcion: string, rfc: string): ObligacionFiscal | undefined {
  const norm = normalizeForMatch(descripcion);
  const esPM = rfc.length === 12;

  for (const rule of CATALOG_MATCH_RULES) {
    for (const kwSet of rule.keywords) {
      const allMatch = kwSet.every(kw => norm.includes(normalizeForMatch(kw)));
      if (allMatch) {
        // Special case: RESICO ISR provisional — PM vs PF
        if (rule.id === 'isr-resico-pm' && !esPM) {
          return OBLIGACIONES_CATALOGO.find(c => c.id === 'isr-resico-pf');
        }
        if (rule.id === 'isr-resico-pf' && esPM) {
          return OBLIGACIONES_CATALOGO.find(c => c.id === 'isr-resico-pm');
        }
        return OBLIGACIONES_CATALOGO.find(c => c.id === rule.id);
      }
    }
  }
  return undefined;
}

export interface ObligacionContribuyente {
  id: string;
  contribuyenteId: string;
  catalogoId: string | null;
  nombre: string;
  fundamento: string | null;
  frecuencia: string | null;
  fechaLimite: string | null;
  categoria: string | null;
  activa: boolean;
  esRecomendada: boolean;
  esCustom: boolean;
  completada: boolean;
  completadaAt: string | null;
  completadaPor: string | null;
  periodoCompletado: string | null;
  createdAt?: string;
}

export function getCatalogo(): ObligacionFiscal[] {
  return OBLIGACIONES_CATALOGO;
}

export async function getObligaciones(pool: Pool, contribuyenteId: string): Promise<ObligacionContribuyente[]> {
  const { rows } = await pool.query(`
    SELECT id, contribuyente_id AS "contribuyenteId", catalogo_id AS "catalogoId",
      nombre, fundamento, frecuencia, fecha_limite AS "fechaLimite", categoria,
      activa, es_recomendada AS "esRecomendada", es_custom AS "esCustom",
      completada, completada_at AS "completadaAt", completada_por AS "completadaPor",
      periodo_completado AS "periodoCompletado",
      created_at AS "createdAt"
    FROM obligaciones_contribuyente
    WHERE contribuyente_id = $1
    ORDER BY categoria, nombre
  `, [contribuyenteId]);
  return rows;
}

/**
 * Reads obligations from the latest Constancia de Situación Fiscal (CSF)
 * and populates obligaciones_contribuyente. Falls back to catalog-based
 * recommendations if no CSF exists.
 */
export async function initRecomendaciones(
  pool: Pool,
  contribuyenteId: string,
  rfc: string,
  regimenes: string[],
  tieneNomina: boolean
): Promise<number> {
  // Clean up alerts and periodos for existing recommended obligations before replacing
  await pool.query(
    `DELETE FROM alertas WHERE tipo LIKE 'ob-%' AND SUBSTRING(tipo FROM 4 FOR 36) IN (
      SELECT id::text FROM obligaciones_contribuyente WHERE contribuyente_id = $1 AND es_recomendada = true
    )`,
    [contribuyenteId],
  );
  await pool.query(
    `DELETE FROM obligacion_periodos WHERE obligacion_id IN (
      SELECT id FROM obligaciones_contribuyente WHERE contribuyente_id = $1 AND es_recomendada = true
    )`,
    [contribuyenteId],
  );
  // Clear previous recommended obligations (re-init replaces them)
  await pool.query(
    `DELETE FROM obligaciones_contribuyente WHERE contribuyente_id = $1 AND es_recomendada = true`,
    [contribuyenteId],
  );

  // Try to get obligations from the latest CSF
  const { rows: csfRows } = await pool.query(`
    SELECT datos->'obligaciones' as obligaciones
    FROM constancias_situacion_fiscal
    WHERE rfc = $1
    ORDER BY created_at DESC LIMIT 1
  `, [rfc]);

  const csfObligaciones = csfRows[0]?.obligaciones as Array<{
    descripcion: string;
    fechaInicio: string;
    fechaFin?: string;
    descripcionVencimiento: string;
  }> | null;

  let count = 0;

  if (csfObligaciones && csfObligaciones.length > 0) {
    // Use CSF obligations directly — these are the official SAT obligations
    // Only import ACTIVE obligations (no fechaFin = still in effect)
    const activeCsf = csfObligaciones.filter(ob => !ob.fechaFin);
    for (const ob of activeCsf) {
      // Keyword-based matching against catalog for enrichment (fundamento, categoria)
      const catalogMatch = matchCsfToCatalog(ob.descripcion, rfc);

      const { rowCount } = await pool.query(`
        INSERT INTO obligaciones_contribuyente (
          contribuyente_id, catalogo_id, nombre, fundamento, frecuencia, fecha_limite, categoria, es_recomendada
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, true)
        ON CONFLICT DO NOTHING
      `, [
        contribuyenteId,
        catalogMatch?.id || null,
        ob.descripcion,
        catalogMatch?.fundamento || null,
        catalogMatch?.frecuencia || inferirFrecuencia(ob.descripcionVencimiento),
        ob.descripcionVencimiento,
        catalogMatch?.categoria || 'SAT',
      ]);
      count += rowCount ?? 0;
    }
  } else {
    // Fallback: use catalog-based recommendations
    const recomendadas = getRecomendaciones(rfc, regimenes, tieneNomina);
    for (const ob of recomendadas) {
      const { rowCount } = await pool.query(`
        INSERT INTO obligaciones_contribuyente (contribuyente_id, catalogo_id, nombre, fundamento, frecuencia, fecha_limite, categoria, es_recomendada)
        VALUES ($1, $2, $3, $4, $5, $6, $7, true)
        ON CONFLICT DO NOTHING
      `, [contribuyenteId, ob.id, ob.nombre, ob.fundamento, ob.frecuencia, ob.fechaLimite, ob.categoria]);
      count += rowCount ?? 0;
    }
  }

  return count;
}

function inferirFrecuencia(vencimiento: string): string {
  const lower = vencimiento.toLowerCase();
  if (lower.includes('mensual') || lower.includes('mes')) return 'mensual';
  if (lower.includes('bimest')) return 'bimestral';
  if (lower.includes('trimest')) return 'trimestral';
  if (lower.includes('anual') || lower.includes('ejercicio') || lower.includes('tres meses siguientes')) return 'anual';
  return 'mensual';
}

export async function completeObligacion(pool: Pool, obligacionId: string, userId: string, periodo: string): Promise<boolean> {
  const { rowCount } = await pool.query(
    'UPDATE obligaciones_contribuyente SET completada = true, completada_at = now(), completada_por = $2, periodo_completado = $3 WHERE id = $1',
    [obligacionId, userId, periodo]
  );
  return (rowCount ?? 0) > 0;
}

export async function uncompleteObligacion(pool: Pool, obligacionId: string): Promise<boolean> {
  const { rowCount } = await pool.query(
    'UPDATE obligaciones_contribuyente SET completada = false, completada_at = null, completada_por = null, periodo_completado = null WHERE id = $1',
    [obligacionId]
  );
  return (rowCount ?? 0) > 0;
}

export async function addObligacion(pool: Pool, contribuyenteId: string, data: {
  catalogoId?: string;
  nombre: string;
  fundamento?: string;
  frecuencia?: string;
  fechaLimite?: string;
  categoria?: string;
}): Promise<ObligacionContribuyente> {
  const isFromCatalog = !!data.catalogoId;
  const { rows: [row] } = await pool.query(`
    INSERT INTO obligaciones_contribuyente (contribuyente_id, catalogo_id, nombre, fundamento, frecuencia, fecha_limite, categoria, es_custom)
    VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
    RETURNING id, contribuyente_id AS "contribuyenteId", catalogo_id AS "catalogoId",
      nombre, fundamento, frecuencia, fecha_limite AS "fechaLimite", categoria,
      activa, es_recomendada AS "esRecomendada", es_custom AS "esCustom"
  `, [contribuyenteId, data.catalogoId || null, data.nombre, data.fundamento || null,
      data.frecuencia || null, data.fechaLimite || null, data.categoria || 'Custom',
      !isFromCatalog]);
  return row;
}

export async function removeObligacion(pool: Pool, obligacionId: string): Promise<boolean> {
  const { rowCount } = await pool.query(
    'UPDATE obligaciones_contribuyente SET activa = false WHERE id = $1',
    [obligacionId]
  );
  // Clean up alerts generated for this obligation (tipo format: 'ob-{obligacionId}-{periodo}')
  await pool.query(
    `DELETE FROM alertas WHERE tipo LIKE $1`,
    [`ob-${obligacionId}-%`],
  );
  // Clean up completion records
  await pool.query(
    'DELETE FROM obligacion_periodos WHERE obligacion_id = $1',
    [obligacionId],
  );
  return (rowCount ?? 0) > 0;
}

export async function restoreObligacion(pool: Pool, obligacionId: string): Promise<boolean> {
  const { rowCount } = await pool.query(
    'UPDATE obligaciones_contribuyente SET activa = true WHERE id = $1',
    [obligacionId]
  );
  return (rowCount ?? 0) > 0;
}

/**
 * Returns obligations for a specific period (YYYY-MM) for a contribuyente.
 * Includes:
 * - All active obligations that apply to this period (based on frequency)
 * - Completion status from obligacion_periodos table
 * - Past-due obligations from previous periods that were NOT completed
 */
export interface DeclaracionLink {
  id: number;
  año: number;
  mes: number;
  tipo: 'normal' | 'complementaria';
  pdfFilename: string | null;
}

export async function getObligacionesPorPeriodo(
  pool: Pool,
  contribuyenteId: string,
  periodo: string, // "2026-04"
  incluirAtrasados: boolean = true
): Promise<Array<ObligacionContribuyente & { periodStatus: 'pendiente' | 'completada' | 'atrasada'; periodoAplica: string; declaracion: DeclaracionLink | null }>> {
  // Get all active obligations for this contribuyente
  const obligaciones = await getObligaciones(pool, contribuyenteId);
  const activas = obligaciones.filter(o => o.activa);

  const [year, month] = periodo.split('-').map(Number);
  const currentPeriodo = new Date().toISOString().substring(0, 7);
  const results: Array<ObligacionContribuyente & { periodStatus: string; periodoAplica: string; declaracion: DeclaracionLink | null }> = [];

  // Get all completion records + associated declaration info for this contribuyente
  const { rows: completions } = await pool.query<{
    obligacion_id: string;
    periodo: string;
    completada: boolean;
    declaracion_id: number | null;
    decl_año: number | null;
    decl_mes: number | null;
    decl_tipo: 'normal' | 'complementaria' | null;
    decl_pdf_filename: string | null;
  }>(`
    SELECT op.obligacion_id, op.periodo, op.completada,
           op.declaracion_id,
           dp.año  AS decl_año,
           dp.mes  AS decl_mes,
           dp.tipo AS decl_tipo,
           dp.pdf_filename AS decl_pdf_filename
    FROM obligacion_periodos op
    JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id
    LEFT JOIN declaraciones_provisionales dp ON dp.id = op.declaracion_id
    WHERE oc.contribuyente_id = $1
  `, [contribuyenteId]);

  const completionMap = new Map<string, boolean>();
  const declaracionMap = new Map<string, DeclaracionLink | null>();
  for (const c of completions) {
    const key = `${c.obligacion_id}:${c.periodo}`;
    completionMap.set(key, c.completada);
    if (c.declaracion_id && c.decl_año != null && c.decl_mes != null && c.decl_tipo) {
      declaracionMap.set(key, {
        id: c.declaracion_id,
        año: c.decl_año,
        mes: c.decl_mes,
        tipo: c.decl_tipo,
        pdfFilename: c.decl_pdf_filename,
      });
    }
  }

  for (const ob of activas) {
    // Obligations only apply from the month they were created forward
    const obStartPeriodo = ob.createdAt
      ? new Date(ob.createdAt).toISOString().substring(0, 7)
      : '2000-01';

    // Check if this obligation applies to the requested period
    if (periodo >= obStartPeriodo && appliesTo(ob.frecuencia, periodo)) {
      const key = `${ob.id}:${periodo}`;
      const isCompleted = completionMap.get(key) === true;
      results.push({
        ...ob,
        periodStatus: isCompleted ? 'completada' : 'pendiente',
        periodoAplica: periodo,
        declaracion: declaracionMap.get(key) ?? null,
      });
    }

    // Check past-due (previous periods not completed) — only if requested
    if (incluirAtrasados && periodo >= currentPeriodo) {
      // Look back up to 12 months for overdue items
      for (let i = 1; i <= 12; i++) {
        let pm = month - i;
        let py = year;
        while (pm < 1) { pm += 12; py--; }
        const pastPeriodo = `${py}-${String(pm).padStart(2, '0')}`;

        if (pastPeriodo >= currentPeriodo) continue; // only past periods
        if (pastPeriodo < obStartPeriodo) continue; // don't go before obligation was created
        if (!appliesTo(ob.frecuencia, pastPeriodo)) continue;

        const pastKey = `${ob.id}:${pastPeriodo}`;
        const pastCompleted = completionMap.get(pastKey) === true;

        if (!pastCompleted) {
          // Don't add duplicates
          if (!results.find(r => r.id === ob.id && r.periodoAplica === pastPeriodo)) {
            results.push({
              ...ob,
              periodStatus: 'atrasada',
              periodoAplica: pastPeriodo,
              declaracion: null,
            });
          }
        }
      }
    }
  }

  // Sort: atrasadas first, then by name
  results.sort((a, b) => {
    if (a.periodStatus === 'atrasada' && b.periodStatus !== 'atrasada') return -1;
    if (b.periodStatus === 'atrasada' && a.periodStatus !== 'atrasada') return 1;
    return a.nombre.localeCompare(b.nombre);
  });

  return results as Array<ObligacionContribuyente & { periodStatus: 'pendiente' | 'completada' | 'atrasada'; periodoAplica: string; declaracion: DeclaracionLink | null }>;
}

function appliesTo(frecuencia: string | null, periodo: string): boolean {
  const [, month] = periodo.split('-').map(Number);
  switch (frecuencia) {
    case 'mensual': return true;
    case 'bimestral': return month % 2 === 1; // Jan, Mar, May...
    case 'trimestral': return [1, 4, 7, 10].includes(month);
    case 'anual': return month === 3 || month === 4; // March (PM) or April (PF) — show in both
    case 'eventual': return false; // Don't auto-show
    default: return true;
  }
}

/**
 * Mark an obligation as completed for a specific period
 */
export async function completePeriodo(
  pool: Pool,
  obligacionId: string,
  periodo: string,
  userId: string,
  notas?: string
): Promise<boolean> {
  await pool.query(`
    INSERT INTO obligacion_periodos (obligacion_id, periodo, completada, completada_at, completada_por, notas)
    VALUES ($1, $2, true, now(), $3, $4)
    ON CONFLICT (obligacion_id, periodo)
    DO UPDATE SET completada = true, completada_at = now(), completada_por = $3, notas = COALESCE($4, obligacion_periodos.notas)
  `, [obligacionId, periodo, userId, notas || null]);
  return true;
}

/**
 * Unmark an obligation completion for a specific period
 */
export async function uncompletePeriodo(
  pool: Pool,
  obligacionId: string,
  periodo: string
): Promise<boolean> {
  await pool.query(`
    DELETE FROM obligacion_periodos WHERE obligacion_id = $1 AND periodo = $2
  `, [obligacionId, periodo]);
  return true;
}
