import type { Pool } from 'pg';
import { resolveContribuyenteContext } from '../utils/contribuyente-context.js';

/**
 * Activos fijos: CFDIs tipo I con uso_cfdi I01-I08 recibidos por el
 * contribuyente bajo régimen fiscal aplicable. Vista INFORMATIVA — no
 * modifica gastos ni ISR (el sistema sigue tratándolos como gasto del
 * periodo, igual que el SAT). Esta vista permite que el contador haga
 * su seguimiento de deducción mensual proporcional y decida si la
 * aplica o no en su declaración.
 *
 * % anual de deducción según LISR Art. 34. Mensual = anual / 12.
 */
export const PORCENTAJES_ANUALES: Record<string, { concepto: string; pct: number }> = {
  I01: { concepto: 'Construcciones', pct: 0.05 },
  I02: { concepto: 'Mobiliario y equipo de oficina', pct: 0.10 },
  I03: { concepto: 'Equipo de transporte', pct: 0.25 },
  I04: { concepto: 'Equipo de cómputo y accesorios', pct: 0.30 },
  I05: { concepto: 'Dados, troqueles, moldes, matrices', pct: 0.35 },
  I06: { concepto: 'Comunicaciones telefónicas', pct: 0.10 },
  I07: { concepto: 'Comunicaciones satelitales', pct: 0.08 },
  I08: { concepto: 'Otra maquinaria y equipo', pct: 0.10 },
};

const USOS_CFDI = Object.keys(PORCENTAJES_ANUALES);
const REGIMENES_APLICABLES = ['601', '606', '611', '612', '625', '626'];

export type EstadoActivo = 'activo' | 'agotado' | 'baja_venta' | 'baja_desecho' | 'baja_otro';

export interface ActivoFijoItem {
  cfdiId: number;
  uuid: string;
  fechaEmision: string;
  rfcEmisor: string;
  nombreEmisor: string;
  usoCfdi: string;
  concepto: string;
  porcentajeAnual: number;
  porcentajeMensual: number;
  total: number;
  iva: number;
  moi: number;
  acumuladoHastaMesAnterior: number;
  acreditableEsteMes: number;
  saldoPendiente: number;
  estado: EstadoActivo;
  baja: { fechaBaja: string; motivo: string; comentario: string | null } | null;
}

export interface ActivosFijosTotales {
  cantidad: number;
  totalMoi: number;
  totalAcumuladoPrevio: number;
  totalEsteMes: number;
  totalSaldoPendiente: number;
  cantidadActivos: number;
  cantidadAgotados: number;
  cantidadDeBaja: number;
}

function clamp(v: number, lo: number, hi: number): number {
  return Math.max(lo, Math.min(hi, v));
}

function diffMeses(start: Date, end: Date): number {
  return (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth()) + 1;
}

export async function listActivosFijos(
  pool: Pool,
  tenantId: string,
  año: number,
  mes: number,
  contribuyenteId?: string | null,
  filtroEstado?: 'todos' | 'activos' | 'baja' | 'agotados',
): Promise<{ items: ActivoFijoItem[]; totales: ActivosFijosTotales; usosExcluidos: string[] }> {
  const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId);
  const esReceptor = ctx.esReceptor;
  const esPM = ctx.rfcLength === 12;

  // Lee usos excluidos del contribuyente (lista de claves a saltarse, ej.
  // I06/I07 cuando son gastos regulares y no activos fijos reales).
  let usosExcluidos: string[] = [];
  if (contribuyenteId) {
    const { rows } = await pool.query<{ activos_fijos_usos_excluidos: string[] | null }>(
      `SELECT activos_fijos_usos_excluidos FROM contribuyentes WHERE entidad_id = $1`,
      [contribuyenteId.replace(/[^a-f0-9-]/gi, '')],
    );
    usosExcluidos = (rows[0]?.activos_fijos_usos_excluidos ?? []).filter(u => USOS_CFDI.includes(u));
  }
  const usosAplicables = USOS_CFDI.filter(u => !usosExcluidos.includes(u));

  // Filtro de régimen: 626 solo aplica si el contribuyente es PM.
  const regsAplicables = esPM ? REGIMENES_APLICABLES : REGIMENES_APLICABLES.filter(r => r !== '626');

  const usosArray = `ARRAY[${usosAplicables.map(u => `'${u}'`).join(',')}]`;
  const regsArray = `ARRAY[${regsAplicables.map(r => `'${r}'`).join(',')}]`;

  const { rows } = await pool.query(
    `SELECT c.id AS cfdi_id, c.uuid, c.fecha_emision, c.rfc_emisor, c.nombre_emisor,
            c.uso_cfdi, c.total_mxn, c.iva_traslado_mxn, c.ieps_traslado_mxn,
            c.impuestos_locales_trasladado_mxn, c.regimen_fiscal_receptor,
            b.fecha_baja, b.motivo, b.comentario
       FROM cfdis c
       LEFT JOIN activos_fijos_baja b ON b.cfdi_id = c.id
      WHERE ${esReceptor}
        AND c.tipo_comprobante = 'I'
        AND c.uso_cfdi = ANY(${usosArray})
        AND c.regimen_fiscal_receptor = ANY(${regsArray})
        AND c.status NOT IN ('Cancelado','0')
      ORDER BY c.fecha_emision DESC`,
  );

  const items: ActivoFijoItem[] = [];
  const periodoFin = new Date(año, mes - 1, 1); // primer día del mes filtrado

  for (const r of rows) {
    const fechaEmision = new Date(r.fecha_emision);
    const moi = Number(r.total_mxn ?? 0)
      - Number(r.iva_traslado_mxn ?? 0)
      - Number(r.ieps_traslado_mxn ?? 0)
      - Number(r.impuestos_locales_trasladado_mxn ?? 0);
    const meta = PORCENTAJES_ANUALES[r.uso_cfdi];
    if (!meta) continue;
    const pctAnual = meta.pct;
    const pctMensual = pctAnual / 12;

    // Fecha de baja (si existe) limita los meses aplicables
    const fechaBaja = r.fecha_baja ? new Date(r.fecha_baja) : null;

    // Mes ancla del periodo filtrado
    const mesEjAnchor = new Date(año, mes - 1, 1);
    const mesAdqAnchor = new Date(fechaEmision.getFullYear(), fechaEmision.getMonth(), 1);

    // Meses transcurridos hasta el mes filtrado (incluido)
    let mesesHasta = diffMeses(mesAdqAnchor, mesEjAnchor);
    let mesesHastaPrev = mesesHasta - 1;

    // Recortar si hay baja: máximo el mes de la baja inclusive
    if (fechaBaja) {
      const mesBaja = new Date(fechaBaja.getFullYear(), fechaBaja.getMonth(), 1);
      const mesesHastaBaja = diffMeses(mesAdqAnchor, mesBaja);
      mesesHasta = Math.min(mesesHasta, mesesHastaBaja);
      mesesHastaPrev = Math.min(mesesHastaPrev, mesesHastaBaja);
    }

    mesesHasta = Math.max(0, mesesHasta);
    mesesHastaPrev = Math.max(0, mesesHastaPrev);

    const acumHasta = clamp(moi * pctMensual * mesesHasta, 0, moi);
    const acumPrev = clamp(moi * pctMensual * mesesHastaPrev, 0, moi);
    const acreditable = Math.max(0, acumHasta - acumPrev);
    const saldo = Math.max(0, moi - acumHasta);

    let estado: EstadoActivo = 'activo';
    if (fechaBaja) {
      estado = `baja_${r.motivo}` as EstadoActivo;
    } else if (saldo === 0) {
      estado = 'agotado';
    }

    if (filtroEstado === 'activos' && estado !== 'activo') continue;
    if (filtroEstado === 'agotados' && estado !== 'agotado') continue;
    if (filtroEstado === 'baja' && !estado.startsWith('baja_')) continue;

    items.push({
      cfdiId: r.cfdi_id,
      uuid: r.uuid,
      fechaEmision: fechaEmision.toISOString().slice(0, 10),
      rfcEmisor: r.rfc_emisor,
      nombreEmisor: r.nombre_emisor ?? '',
      usoCfdi: r.uso_cfdi,
      concepto: meta.concepto,
      porcentajeAnual: pctAnual,
      porcentajeMensual: pctMensual,
      total: Number(r.total_mxn ?? 0),
      iva: Number(r.iva_traslado_mxn ?? 0),
      moi,
      acumuladoHastaMesAnterior: Math.round(acumPrev * 100) / 100,
      acreditableEsteMes: Math.round(acreditable * 100) / 100,
      saldoPendiente: Math.round(saldo * 100) / 100,
      estado,
      baja: fechaBaja
        ? { fechaBaja: fechaBaja.toISOString().slice(0, 10), motivo: r.motivo, comentario: r.comentario }
        : null,
    });
  }

  const totales: ActivosFijosTotales = {
    cantidad: items.length,
    totalMoi: 0,
    totalAcumuladoPrevio: 0,
    totalEsteMes: 0,
    totalSaldoPendiente: 0,
    cantidadActivos: 0,
    cantidadAgotados: 0,
    cantidadDeBaja: 0,
  };
  for (const i of items) {
    totales.totalMoi += i.moi;
    totales.totalAcumuladoPrevio += i.acumuladoHastaMesAnterior;
    totales.totalEsteMes += i.acreditableEsteMes;
    totales.totalSaldoPendiente += i.saldoPendiente;
    if (i.estado === 'activo') totales.cantidadActivos++;
    else if (i.estado === 'agotado') totales.cantidadAgotados++;
    else totales.cantidadDeBaja++;
  }
  totales.totalMoi = Math.round(totales.totalMoi * 100) / 100;
  totales.totalAcumuladoPrevio = Math.round(totales.totalAcumuladoPrevio * 100) / 100;
  totales.totalEsteMes = Math.round(totales.totalEsteMes * 100) / 100;
  totales.totalSaldoPendiente = Math.round(totales.totalSaldoPendiente * 100) / 100;

  return { items, totales, usosExcluidos };
}

/** Lee los usos CFDI excluidos para un contribuyente. */
export async function getUsosExcluidos(pool: Pool, contribuyenteId: string): Promise<string[]> {
  const { rows } = await pool.query<{ activos_fijos_usos_excluidos: string[] | null }>(
    `SELECT activos_fijos_usos_excluidos FROM contribuyentes WHERE entidad_id = $1`,
    [contribuyenteId.replace(/[^a-f0-9-]/gi, '')],
  );
  return (rows[0]?.activos_fijos_usos_excluidos ?? []).filter(u => USOS_CFDI.includes(u));
}

/** Guarda la lista de usos excluidos (filtra a I01-I08 y deduplica). */
export async function setUsosExcluidos(
  pool: Pool,
  contribuyenteId: string,
  usos: string[],
): Promise<string[]> {
  const valid = [...new Set(usos.filter(u => USOS_CFDI.includes(u)))];
  await pool.query(
    `UPDATE contribuyentes SET activos_fijos_usos_excluidos = $2::jsonb WHERE entidad_id = $1`,
    [contribuyenteId.replace(/[^a-f0-9-]/gi, ''), JSON.stringify(valid)],
  );
  return valid;
}

export async function darDeBaja(
  pool: Pool,
  cfdiId: number,
  fechaBaja: string,
  motivo: 'venta' | 'desecho' | 'otro',
  userId: string,
  comentario: string | null,
): Promise<void> {
  await pool.query(
    `INSERT INTO activos_fijos_baja (cfdi_id, fecha_baja, motivo, comentario, dado_de_baja_por)
     VALUES ($1, $2, $3, $4, $5)
     ON CONFLICT (cfdi_id) DO UPDATE
       SET fecha_baja = EXCLUDED.fecha_baja,
           motivo = EXCLUDED.motivo,
           comentario = EXCLUDED.comentario,
           dado_de_baja_por = EXCLUDED.dado_de_baja_por`,
    [cfdiId, fechaBaja, motivo, comentario, userId],
  );
}

export async function revertirBaja(pool: Pool, cfdiId: number): Promise<boolean> {
  const { rowCount } = await pool.query(
    `DELETE FROM activos_fijos_baja WHERE cfdi_id = $1`,
    [cfdiId],
  );
  return (rowCount ?? 0) > 0;
}
