import type { Pool } from 'pg';
import { prisma } from '../config/database.js';

export interface CreateContribuyenteData {
  rfc: string;
  razonSocial: string;
  regimenFiscal?: string;
  codigoPostal?: string;
  domicilio?: Record<string, unknown>;
  supervisorUserId?: string;
}

export interface ContribuyenteRow {
  id: string;
  tipo: string;
  nombre: string;
  identificador: string;
  supervisorUserId: string | null;
  active: boolean;
  createdAt: string;
  rfc: string;
  regimenFiscal: string | null;
  codigoPostal: string | null;
  domicilio: Record<string, unknown> | null;
}

async function fetchTenantFiscalData(tenantId: string) {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: {
      rfc: true,
      codigoPostal: true,
      calle: true,
      numExterior: true,
      numInterior: true,
      colonia: true,
      ciudad: true,
      municipio: true,
      estado: true,
      telefono: true,
    },
  });
  if (!tenant) return null;

  const regimenes = await prisma.tenantRegimenActivo.findMany({
    where: { tenantId },
    select: { regimen: { select: { clave: true } } },
  });
  const regimenFiscal = regimenes.map(r => r.regimen.clave).join(',') || null;

  const hasAnyAddress = tenant.calle || tenant.colonia || tenant.ciudad || tenant.municipio || tenant.estado || tenant.codigoPostal;
  const domicilio = hasAnyAddress
    ? {
        calle: tenant.calle || '',
        numExterior: tenant.numExterior || '',
        numInterior: tenant.numInterior || '',
        colonia: tenant.colonia || '',
        ciudad: tenant.ciudad || '',
        municipio: tenant.municipio || '',
        estado: tenant.estado || '',
        codigoPostal: tenant.codigoPostal || '',
        telefono: tenant.telefono || '',
      }
    : null;

  return { tenantRfc: tenant.rfc, regimenFiscal, codigoPostal: tenant.codigoPostal, domicilio };
}

function mergeContribuyenteWithTenant(
  row: ContribuyenteRow,
  tenantData: NonNullable<Awaited<ReturnType<typeof fetchTenantFiscalData>>>
): ContribuyenteRow {
  return {
    ...row,
    regimenFiscal: row.regimenFiscal || tenantData.regimenFiscal,
    codigoPostal: row.codigoPostal || tenantData.codigoPostal,
    domicilio: row.domicilio || tenantData.domicilio,
  };
}

export async function listContribuyentes(pool: Pool, entidadIds?: string[], tenantId?: string): Promise<ContribuyenteRow[]> {
  let query = `
    SELECT
      e.id, e.tipo, e.nombre, e.identificador,
      e.supervisor_user_id AS "supervisorUserId",
      e.active, e.created_at AS "createdAt",
      c.rfc, c.regimen_fiscal AS "regimenFiscal",
      c.codigo_postal AS "codigoPostal", c.domicilio
    FROM entidades_gestionadas e
    JOIN contribuyentes c ON c.entidad_id = e.id
    WHERE e.active = true
  `;
  const params: unknown[] = [];

  if (entidadIds !== undefined) {
    if (entidadIds.length === 0) return []; // No access = empty list
    query += ` AND e.id = ANY($1)`;
    params.push(entidadIds);
  }

  query += ' ORDER BY e.created_at DESC';
  const { rows } = await pool.query(query, params);

  if (!tenantId) return rows;

  const tenantData = await fetchTenantFiscalData(tenantId);
  if (!tenantData) return rows;

  return rows.map((r: ContribuyenteRow) => {
    if (r.rfc !== tenantData.tenantRfc) return r;
    if (r.regimenFiscal && r.codigoPostal && r.domicilio) return r;
    return mergeContribuyenteWithTenant(r, tenantData);
  });
}

export async function getContribuyenteById(pool: Pool, id: string, tenantId?: string): Promise<ContribuyenteRow | null> {
  const { rows } = await pool.query(`
    SELECT
      e.id, e.tipo, e.nombre, e.identificador,
      e.supervisor_user_id AS "supervisorUserId",
      e.active, e.created_at AS "createdAt",
      c.rfc, c.regimen_fiscal AS "regimenFiscal",
      c.codigo_postal AS "codigoPostal", c.domicilio
    FROM entidades_gestionadas e
    JOIN contribuyentes c ON c.entidad_id = e.id
    WHERE e.id = $1
  `, [id]);
  const row = rows[0] ?? null;
  if (!row || !tenantId) return row;

  const tenantData = await fetchTenantFiscalData(tenantId);
  if (!tenantData || row.rfc !== tenantData.tenantRfc) return row;
  if (row.regimenFiscal && row.codigoPostal && row.domicilio) return row;

  return mergeContribuyenteWithTenant(row, tenantData);
}

export async function createContribuyente(
  pool: Pool,
  data: CreateContribuyenteData,
): Promise<{ row: ContribuyenteRow; reactivated: boolean }> {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // 1. Si el RFC ya existe, reactivar la entidad desactivada en lugar de
    //    violar el UNIQUE de contribuyentes.rfc. Si está activa, lanzar
    //    error 23505 para que el controller devuelva 409.
    const { rows: existing } = await client.query<{ entidad_id: string; active: boolean }>(`
      SELECT c.entidad_id, e.active
      FROM contribuyentes c
      JOIN entidades_gestionadas e ON e.id = c.entidad_id
      WHERE UPPER(c.rfc) = UPPER($1)
    `, [data.rfc]);

    if (existing.length > 0) {
      const { entidad_id, active } = existing[0];
      if (active) {
        await client.query('ROLLBACK');
        const err: any = new Error('Ya existe un contribuyente activo con este RFC');
        err.code = '23505';
        throw err;
      }

      await client.query(`
        UPDATE entidades_gestionadas
        SET active = true,
            nombre = $1,
            identificador = $2,
            supervisor_user_id = $3,
            updated_at = now()
        WHERE id = $4
      `, [data.razonSocial, data.rfc.toUpperCase(), data.supervisorUserId ?? null, entidad_id]);

      await client.query(`
        UPDATE contribuyentes
        SET regimen_fiscal = $1,
            codigo_postal = $2,
            domicilio = $3
        WHERE entidad_id = $4
      `, [data.regimenFiscal ?? null, data.codigoPostal ?? null, data.domicilio ? JSON.stringify(data.domicilio) : null, entidad_id]);

      await client.query('COMMIT');

      await backfillCfdiContribuyente(pool, entidad_id, data.rfc.toUpperCase()).catch(
        (err) => console.error('[Contribuyente] Backfill CFDIs failed (non-blocking):', err)
      );

      return { row: (await getContribuyenteById(pool, entidad_id))!, reactivated: true };
    }

    // 2. Caso normal: crear nuevo contribuyente
    const { rows: [entidad] } = await client.query(`
      INSERT INTO entidades_gestionadas (tipo, nombre, identificador, supervisor_user_id)
      VALUES ('CONTRIBUYENTE', $1, $2, $3)
      RETURNING id
    `, [data.razonSocial, data.rfc.toUpperCase(), data.supervisorUserId ?? null]);

    await client.query(`
      INSERT INTO contribuyentes (entidad_id, rfc, regimen_fiscal, codigo_postal, domicilio)
      VALUES ($1, $2, $3, $4, $5)
    `, [entidad.id, data.rfc.toUpperCase(), data.regimenFiscal ?? null, data.codigoPostal ?? null, data.domicilio ? JSON.stringify(data.domicilio) : null]);

    await client.query('COMMIT');

    // Backfill: claim existing CFDIs that match this RFC
    await backfillCfdiContribuyente(pool, entidad.id, data.rfc.toUpperCase()).catch(
      (err) => console.error('[Contribuyente] Backfill CFDIs failed (non-blocking):', err)
    );

    return { row: (await getContribuyenteById(pool, entidad.id))!, reactivated: false };
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}

export async function updateContribuyente(pool: Pool, id: string, data: Partial<CreateContribuyenteData>): Promise<ContribuyenteRow | null> {
  const existing = await getContribuyenteById(pool, id);
  if (!existing) return null;

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // Update entidades_gestionadas if needed
    const entidadSets: string[] = [];
    const entidadVals: unknown[] = [];
    let idx = 1;

    if (data.razonSocial) {
      entidadSets.push(`nombre = $${idx}`, `identificador = $${idx}`);
      entidadVals.push(data.razonSocial);
      idx++;
    }
    if (data.supervisorUserId !== undefined) {
      entidadSets.push(`supervisor_user_id = $${idx}`);
      entidadVals.push(data.supervisorUserId);
      idx++;
    }
    if (entidadSets.length > 0) {
      entidadSets.push('updated_at = now()');
      entidadVals.push(id);
      await client.query(`UPDATE entidades_gestionadas SET ${entidadSets.join(', ')} WHERE id = $${idx}`, entidadVals);
    }

    // Update contribuyentes if needed
    const contribSets: string[] = [];
    const contribVals: unknown[] = [];
    idx = 1;

    if (data.regimenFiscal !== undefined) { contribSets.push(`regimen_fiscal = $${idx}`); contribVals.push(data.regimenFiscal); idx++; }
    if (data.codigoPostal !== undefined) { contribSets.push(`codigo_postal = $${idx}`); contribVals.push(data.codigoPostal); idx++; }
    if (data.domicilio !== undefined) { contribSets.push(`domicilio = $${idx}`); contribVals.push(JSON.stringify(data.domicilio)); idx++; }

    if (contribSets.length > 0) {
      contribVals.push(id);
      await client.query(`UPDATE contribuyentes SET ${contribSets.join(', ')} WHERE entidad_id = $${idx}`, contribVals);
    }

    await client.query('COMMIT');
    return (await getContribuyenteById(pool, id))!;
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}

export async function deactivateContribuyente(pool: Pool, id: string): Promise<boolean> {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    const { rowCount } = await client.query(
      'UPDATE entidades_gestionadas SET active = false, updated_at = now() WHERE id = $1',
      [id]
    );
    const ok = (rowCount ?? 0) > 0;

    if (ok) {
      // Limpiar asignaciones para que no aparezca en carteras ni accesos de cliente
      await client.query('DELETE FROM cartera_entidades WHERE entidad_id = $1', [id]).catch((err) => {
        console.error('[Contribuyente] Error limpiando cartera_entidades:', err);
      });
      await client.query('DELETE FROM cliente_accesos WHERE entidad_id = $1', [id]).catch((err) => {
        console.error('[Contribuyente] Error limpiando cliente_accesos:', err);
      });

      // Desactivar FIEL para que no siga sincronizándose mientras está inactivo
      await client.query('UPDATE fiel_contribuyente SET is_active = false WHERE contribuyente_id = $1', [id]).catch((err) => {
        console.error('[Contribuyente] Error desactivando FIEL:', err);
      });
    }

    await client.query('COMMIT');
    return ok;
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}

/**
 * Assigns contribuyente_id to CFDIs that match the RFC (emisor or receptor).
 * Runs after contribuyente creation and can be called manually for backfill.
 * Only updates CFDIs where contribuyente_id IS NULL (doesn't override).
 */
export async function backfillCfdiContribuyente(pool: Pool, contribuyenteId: string, rfc: string): Promise<number> {
  const { rowCount } = await pool.query(`
    UPDATE cfdis
    SET contribuyente_id = $1
    WHERE contribuyente_id IS NULL
      AND (rfc_emisor = $2 OR rfc_receptor = $2)
  `, [contribuyenteId, rfc]);
  const count = rowCount ?? 0;
  if (count > 0) {
    console.log(`[Backfill] Assigned ${count} CFDIs to contribuyente ${rfc} (${contribuyenteId})`);
  }
  return count;
}

/**
 * Backfills ALL contribuyentes in the tenant BD. Useful after initial SAT sync.
 */
export async function backfillAllContribuyentes(pool: Pool): Promise<number> {
  const { rows } = await pool.query('SELECT entidad_id, rfc FROM contribuyentes');
  let total = 0;
  for (const { entidad_id, rfc } of rows) {
    total += await backfillCfdiContribuyente(pool, entidad_id, rfc);
  }
  return total;
}
