/**
 * Script: add-demo-notas-credito.ts
 *
 * Agrega notas de crédito (NC) sintéticas a los contribuyentes del tenant
 * "Demo Ventas" (horux_demoventas). Cada NC se relaciona con una factura
 * existente (tipo_comprobante = 'I', metodo_pago = 'PUE') mediante
 * cfdi_tipo_relacion = '01' y cfdis_relacionados = uuid de la factura origen.
 *
 * El script es idempotente: usa UUIDs deterministas, por lo que volverlo a
 * correr no duplica registros.
 *
 * Uso:
 *   cd apps/api && npx tsx scripts/add-demo-notas-credito.ts
 *
 * Opciones via env:
 *   DEMO_NC_POR_CONTRIBUYENTE=4   # default: 4 (2 emitidas + 2 recibidas)
 *   DEMO_NC_DIAS_DESPUES=90       # default: 90 (max dias despues de la factura)
 */
import { PrismaClient } from '@prisma/client';
import { Pool, type PoolClient } from 'pg';
import { createHash } from 'crypto';
import { tenantDb } from '../src/config/database.ts';
import { markForInvalidation } from '../src/services/metricas.service.js';

const prisma = new PrismaClient();

const DEMO_RFC = 'DEMO2501019X2';
const NC_POR_CONTRIBUYENTE = parseInt(process.env.DEMO_NC_POR_CONTRIBUYENTE || '4', 10);
const MAX_DIAS_DESPUES = parseInt(process.env.DEMO_NC_DIAS_DESPUES || '90', 10);

function deterministicUuid(seed: string): string {
  const hex = createHash('sha256').update(seed).digest('hex');
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
}

function round2(n: number): number {
  return Math.round(n * 100) / 100;
}

function addDays(fecha: Date, dias: number): Date {
  const r = new Date(fecha);
  r.setDate(r.getDate() + dias);
  r.setHours(8 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 60), 0, 0);
  return r;
}

interface FacturaOrigen {
  id: number;
  uuid: string;
  total: number;
  fecha_emision: Date;
  type: 'EMITIDO' | 'RECIBIDO';
  rfc_emisor_id: number;
  rfc_emisor: string;
  nombre_emisor: string;
  rfc_receptor_id: number;
  rfc_receptor: string;
  nombre_receptor: string;
  forma_pago: string;
  uso_cfdi: string;
  regimen_fiscal_emisor: string;
  regimen_fiscal_receptor: string;
}

async function main() {
  console.log(`🌱 Agregando ${NC_POR_CONTRIBUYENTE} notas de crédito por contribuyente en Demo Ventas...\n`);

  const tenant = await prisma.tenant.findUnique({ where: { rfc: DEMO_RFC } });
  if (!tenant) throw new Error(`Tenant ${DEMO_RFC} no encontrado`);

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

  const { rows: contribuyentes } = await pool.query<{ entidad_id: string; rfc: string; nombre: string }>(`
    SELECT c.entidad_id, c.rfc, eg.nombre
    FROM contribuyentes c
    JOIN entidades_gestionadas eg ON eg.id = c.entidad_id
    ORDER BY c.rfc
  `);

  if (contribuyentes.length === 0) throw new Error('No hay contribuyentes demo');

  let totalCreadas = 0;
  let totalExistentes = 0;

  for (const c of contribuyentes) {
    const { creadas, existentes } = await agregarNcContribuyente(pool, c);
    console.log(`✅ ${c.rfc}: ${creadas} NCs creadas, ${existentes} ya existian`);
    totalCreadas += creadas;
    totalExistentes += existentes;
  }

  console.log(`\n🎉 Total: ${totalCreadas} notas de crédito nuevas, ${totalExistentes} ya existian`);
}

async function agregarNcContribuyente(
  pool: Pool,
  contribuyente: { entidad_id: string; rfc: string; nombre: string },
): Promise<{ creadas: number; existentes: number }> {
  const client = await pool.connect();
  const mesesAfectados = new Set<string>();

  try {
    await client.query('BEGIN');

    const mitad = Math.ceil(NC_POR_CONTRIBUYENTE / 2);
    const facturasEmitidas = await obtenerFacturasPUE(client, contribuyente.entidad_id, 'EMITIDO', mitad);
    const facturasRecibidas = await obtenerFacturasPUE(client, contribuyente.entidad_id, 'RECIBIDO', NC_POR_CONTRIBUYENTE - mitad);

    let creadas = 0;
    let existentes = 0;
    const usadas = new Set<string>();
    let idxEmitida = 0;
    let idxRecibida = 0;

    for (let i = 0; i < NC_POR_CONTRIBUYENTE; i++) {
      const esEmitida = i % 2 === 0;
      const origen = esEmitida
        ? facturasEmitidas[idxEmitida++ % facturasEmitidas.length]
        : facturasRecibidas[idxRecibida++ % facturasRecibidas.length];

      if (!origen || usadas.has(origen.uuid)) {
        // Si no hay suficientes facturas distintas, saltar
        continue;
      }
      usadas.add(origen.uuid);

      // Monto de la NC: entre 10% y 40% del total de la factura origen
      const porcentaje = 0.1 + Math.random() * 0.3;
      const ncTotal = round2(origen.total * porcentaje);
      const ncSubtotal = round2(ncTotal / 1.16);
      const ncIva = round2(ncTotal - ncSubtotal);

      // Fecha: entre 5 y MAX_DIAS_DESPUES dias despues de la factura origen, sin pasar de hoy
      const diasDespues = 5 + Math.floor(Math.random() * (MAX_DIAS_DESPUES - 5));
      let ncFecha = addDays(origen.fecha_emision, diasDespues);
      const ahora = new Date();
      if (ncFecha > ahora) ncFecha = ahora;

      const year = String(ncFecha.getFullYear());
      const month = String(ncFecha.getMonth() + 1).padStart(2, '0');
      const fechaStr = ncFecha.toISOString();

      const uuid = deterministicUuid(`${contribuyente.rfc}-demo-nc-${esEmitida ? 'E' : 'R'}-${i}`);
      const { rows: duplicados } = await client.query(
        `SELECT 1 FROM cfdis WHERE lower(uuid) = lower($1) LIMIT 1`,
        [uuid],
      );
      if (duplicados.length > 0) {
        existentes++;
        continue;
      }

      const { rows: [nc] } = await client.query<{ id: number }>(`
        INSERT INTO cfdis (
          year, month, type, uuid, serie, folio, status, fecha_emision,
          rfc_emisor_id, rfc_emisor, nombre_emisor,
          rfc_receptor_id, rfc_receptor, nombre_receptor,
          subtotal, subtotal_mxn, descuento, descuento_mxn,
          total, total_mxn, moneda, tipo_cambio, tipo_comprobante,
          metodo_pago, forma_pago, uso_cfdi,
          iva_traslado, iva_traslado_mxn,
          regimen_fiscal_emisor, regimen_fiscal_receptor,
          contribuyente_id, fecha_efectiva, meses_global, año_global,
          cfdi_tipo_relacion, cfdis_relacionados
        ) VALUES (
          $1, $2, $3, $4, $5, $6, $7, $8,
          $9, $10, $11,
          $12, $13, $14,
          $15, $16, $17, $18,
          $19, $20, $21, $22, $23,
          $24, $25, $26,
          $27, $28,
          $29, $30,
          $31, $32, $33, $34,
          $35, $36
        ) RETURNING id
      `, [
        year, month, esEmitida ? 'EMITIDO' : 'RECIBIDO', uuid, 'NC', String(200000 + i),
        'Vigente', fechaStr,
        origen.rfc_emisor_id, origen.rfc_emisor, origen.nombre_emisor,
        origen.rfc_receptor_id, origen.rfc_receptor, origen.nombre_receptor,
        ncSubtotal, ncSubtotal, 0, 0,
        ncTotal, ncTotal, 'MXN', 1, 'E',
        'PUE', origen.forma_pago, origen.uso_cfdi,
        ncIva, ncIva,
        origen.regimen_fiscal_emisor, origen.regimen_fiscal_receptor,
        contribuyente.entidad_id, fechaStr, month, year,
        '01', origen.uuid.toLowerCase(),
      ]);

      await client.query(`
        INSERT INTO cfdi_conceptos (
          cfdi_id, clave_prod_serv, descripcion, cantidad, clave_unidad, unidad,
          valor_unitario, valor_unitario_mxn, importe, importe_mxn,
          iva_traslado, iva_traslado_mxn
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
      `, [
        nc.id, '84111506', 'Descuento por nota de credito', 1, 'E48', 'Servicio',
        ncSubtotal, ncSubtotal, ncSubtotal, ncSubtotal,
        ncIva, ncIva,
      ]);

      creadas++;
      mesesAfectados.add(`${year}-${month}`);
    }

    for (const ym of mesesAfectados) {
      const [anio, mes] = ym.split('-').map(Number);
      await markForInvalidation(pool, contribuyente.entidad_id, anio, mes, 'demo-nc');
    }

    await client.query('COMMIT');
    return { creadas, existentes };
  } catch (err) {
    await client.query('ROLLBACK').catch(() => {});
    throw err;
  } finally {
    client.release();
  }
}

async function obtenerFacturasPUE(
  client: PoolClient,
  contribuyenteId: string,
  type: 'EMITIDO' | 'RECIBIDO',
  limite: number,
): Promise<FacturaOrigen[]> {
  const { rows } = await client.query<FacturaOrigen>(`
    SELECT
      id, uuid, total_mxn AS total, fecha_emision, type,
      rfc_emisor_id, rfc_emisor, nombre_emisor,
      rfc_receptor_id, rfc_receptor, nombre_receptor,
      forma_pago, uso_cfdi,
      regimen_fiscal_emisor, regimen_fiscal_receptor
    FROM cfdis
    WHERE contribuyente_id = $1
      AND type = $2
      AND tipo_comprobante = 'I'
      AND metodo_pago = 'PUE'
      AND status = 'Vigente'
      AND total_mxn > 5000
    ORDER BY random()
    LIMIT $3
  `, [contribuyenteId, type, Math.max(limite * 3, 20)]);

  // Mezclar y retornar hasta `limite`
  const mezcladas = rows.sort(() => Math.random() - 0.5);
  return mezcladas.slice(0, limite);
}

main()
  .catch((e) => {
    console.error('\n❌ Error:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
    await tenantDb.shutdown();
  });
