import type { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import * as alertasService from '../services/alertas.service.js';
import { generarAlertasAutomaticas, SOSPECHOSA_TIPO_RELACION_WHERE_EXPORT } from '../services/alertas-auto.service.js';
import { sincronizarAlertasManuales, getAlertasManualesPendientes, resolverAlerta } from '../services/alertas-manuales.service.js';
import { getRegimenesActivosClavesEfectivos } from '../services/regimen.service.js';
import { prisma } from '../config/database.js';
import { AppError } from '../middlewares/error.middleware.js';

const createAlertaSchema = z.object({
  tipo: z.enum(['vencimiento', 'discrepancia', 'iva_favor', 'declaracion', 'limite_cfdi', 'custom']),
  titulo: z.string().min(1).max(200),
  mensaje: z.string().min(1).max(2000),
  prioridad: z.enum(['alta', 'media', 'baja']),
  fechaVencimiento: z.string().optional(),
});

const updateAlertaSchema = z.object({
  leida: z.boolean().optional(),
  resuelta: z.boolean().optional(),
});

export async function getAlertas(req: Request, res: Response, next: NextFunction) {
  try {
    const { leida, resuelta, prioridad } = req.query;
    const alertas = await alertasService.getAlertas(req.tenantPool!, {
      leida: leida === 'true' ? true : leida === 'false' ? false : undefined,
      resuelta: resuelta === 'true' ? true : resuelta === 'false' ? false : undefined,
      prioridad: prioridad as string,
    });
    res.json(alertas);
  } catch (error) {
    next(error);
  }
}

export async function getAlerta(req: Request, res: Response, next: NextFunction) {
  try {
    const alerta = await alertasService.getAlertaById(req.tenantPool!, parseInt(String(req.params.id)));
    if (!alerta) {
      return res.status(404).json({ message: 'Alerta no encontrada' });
    }
    res.json(alerta);
  } catch (error) {
    next(error);
  }
}

export async function createAlerta(req: Request, res: Response, next: NextFunction) {
  try {
    const data = createAlertaSchema.parse(req.body);
    const alerta = await alertasService.createAlerta(req.tenantPool!, data);
    res.status(201).json(alerta);
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function updateAlerta(req: Request, res: Response, next: NextFunction) {
  try {
    const data = updateAlertaSchema.parse(req.body);
    const alerta = await alertasService.updateAlerta(req.tenantPool!, parseInt(String(req.params.id)), data);
    res.json(alerta);
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function deleteAlerta(req: Request, res: Response, next: NextFunction) {
  try {
    await alertasService.deleteAlerta(req.tenantPool!, parseInt(String(req.params.id)));
    res.status(204).send();
  } catch (error) {
    next(error);
  }
}

export async function getStats(req: Request, res: Response, next: NextFunction) {
  try {
    const stats = await alertasService.getStats(req.tenantPool!);
    res.json(stats);
  } catch (error) {
    next(error);
  }
}

export async function markAllAsRead(req: Request, res: Response, next: NextFunction) {
  try {
    await alertasService.markAllAsRead(req.tenantPool!);
    res.json({ success: true });
  } catch (error) {
    next(error);
  }
}

export async function getManualesPendientes(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    // Sincronizar primero (crear alertas para eventos vencidos nuevos)
    await sincronizarAlertasManuales(req.tenantPool!, req.user!.tenantId, contribuyenteId || null);
    // Devolver pendientes (filtered by contribuyente or user role)
    const alertas = await getAlertasManualesPendientes(
      req.tenantPool!,
      contribuyenteId || null,
      req.user!.userId,
      req.user!.role,
    );
    res.json(alertas);
  } catch (error) {
    next(error);
  }
}

export async function resolverAlertaManual(req: Request, res: Response, next: NextFunction) {
  try {
    await resolverAlerta(req.tenantPool!, String(req.params.id));
    res.json({ success: true });
  } catch (error) {
    next(error);
  }
}

export async function getAlertasAutomaticas(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    console.log(`[AlertasCtrl] GET /automaticas tenant=${req.user!.tenantId} contribuyente=${contribuyenteId || 'null'} user=${req.user!.userId} role=${req.user!.role}`);
    const alertas = await generarAlertasAutomaticas(req.tenantPool!, req.user!.tenantId, contribuyenteId || null);
    console.log(`[AlertasCtrl] GET /automaticas devuelve ${alertas.length} alertas: ${alertas.map(a => a.id).join(', ') || 'ninguna'}`);
    res.json(alertas);
  } catch (error) {
    next(error);
  }
}

// Drill-down: Clientes en lista negra
export async function getListaNegraClientes(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const listaRfcs = await prisma.listaNegra.findMany({
      where: { situacion: { in: ['Definitivo', 'Presunto'] } },
      select: { rfc: true, nombre: true, situacion: true },
    });
    const rfcMap = new Map(listaRfcs.map(l => [l.rfc, l]));

    const { rows } = await req.tenantPool!.query(`
      SELECT rfc_receptor as rfc, nombre_receptor as nombre,
        COUNT(*)::int as cantidad, SUM(total_mxn) as total
      FROM cfdis
      WHERE type = 'EMITIDO' AND status NOT IN ('Cancelado', '0') AND tipo_comprobante = 'I'
      ${cf}
      GROUP BY rfc_receptor, nombre_receptor
      ORDER BY total DESC
    `);

    const result = rows
      .filter((r: any) => rfcMap.has(r.rfc))
      .map((r: any) => ({
        rfc: r.rfc,
        nombre: r.nombre,
        cantidad: r.cantidad,
        total: Number(r.total),
        situacionSat: rfcMap.get(r.rfc)!.situacion,
      }));

    res.json(result);
  } catch (error) {
    next(error);
  }
}

// Drill-down: Proveedores en lista negra
export async function getListaNegraProveedores(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const listaRfcs = await prisma.listaNegra.findMany({
      where: { situacion: { in: ['Definitivo', 'Presunto'] } },
      select: { rfc: true, nombre: true, situacion: true },
    });
    const rfcMap = new Map(listaRfcs.map(l => [l.rfc, l]));

    const { rows } = await req.tenantPool!.query(`
      SELECT rfc_emisor as rfc, nombre_emisor as nombre,
        COUNT(*)::int as cantidad, SUM(total_mxn) as total
      FROM cfdis
      WHERE type = 'RECIBIDO' AND status NOT IN ('Cancelado', '0') AND tipo_comprobante = 'I'
      ${cf}
      GROUP BY rfc_emisor, nombre_emisor
      ORDER BY total DESC
    `);

    const result = rows
      .filter((r: any) => rfcMap.has(r.rfc))
      .map((r: any) => ({
        rfc: r.rfc,
        nombre: r.nombre,
        cantidad: r.cantidad,
        total: Number(r.total),
        situacionSat: rfcMap.get(r.rfc)!.situacion,
      }));

    res.json(result);
  } catch (error) {
    next(error);
  }
}

// Drill-down: Concentración de clientes
export async function getConcentracionClientes(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const { rows } = await req.tenantPool!.query(`
      SELECT rfc_receptor as rfc, nombre_receptor as nombre,
        COUNT(*)::int as cantidad,
        SUM(total_mxn) as total
      FROM cfdis
      WHERE type = 'EMITIDO' AND tipo_comprobante = 'I'
        AND status NOT IN ('Cancelado', '0') AND total_mxn > 0
        ${cf}
      GROUP BY rfc_receptor, nombre_receptor
      ORDER BY total DESC
    `);

    const totalGeneral = rows.reduce((s: number, r: any) => s + Number(r.total), 0);
    const result = rows.map((r: any) => ({
      rfc: r.rfc,
      nombre: r.nombre,
      cantidad: r.cantidad,
      total: Number(r.total),
      participacion: totalGeneral > 0 ? Math.round((Number(r.total) / totalGeneral) * 10000) / 100 : 0,
    }));

    res.json(result);
  } catch (error) {
    next(error);
  }
}

// Drill-down: Concentración de proveedores
export async function getConcentracionProveedores(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const { rows } = await req.tenantPool!.query(`
      SELECT rfc_emisor as rfc, nombre_emisor as nombre,
        COUNT(*)::int as cantidad,
        SUM(total_mxn) as total
      FROM cfdis
      WHERE type = 'RECIBIDO' AND tipo_comprobante = 'I'
        AND status NOT IN ('Cancelado', '0') AND total_mxn > 0
        ${cf}
      GROUP BY rfc_emisor, nombre_emisor
      ORDER BY total DESC
    `);

    const totalGeneral = rows.reduce((s: number, r: any) => s + Number(r.total), 0);
    const result = rows.map((r: any) => ({
      rfc: r.rfc,
      nombre: r.nombre,
      cantidad: r.cantidad,
      total: Number(r.total),
      participacion: totalGeneral > 0 ? Math.round((Number(r.total) / totalGeneral) * 10000) / 100 : 0,
    }));

    res.json(result);
  } catch (error) {
    next(error);
  }
}

// Drill-down: CFDIs con discrepancia de régimen
export async function getDiscrepanciaRegimen(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const activos = await getRegimenesActivosClavesEfectivos(req.user!.tenantId, req.tenantPool!, contribuyenteId);
    if (activos.length === 0) return res.json([]);

    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const { rows } = await req.tenantPool!.query(`
      SELECT id, uuid, type, fecha_emision as "fechaEmision",
        rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor",
        rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor",
        total_mxn as "totalMxn", regimen_fiscal_receptor as "regimenReceptor"
      FROM cfdis
      WHERE type = 'RECIBIDO'
        AND status = 'Vigente'
        AND fecha_cancelacion IS NULL
        AND regimen_fiscal_receptor IS NOT NULL
        AND regimen_fiscal_receptor != ALL($1)
        AND id NOT IN (SELECT cfdi_id FROM cfdi_descartados WHERE tipo_alerta = 'discrepancia-regimen')
        ${cf}
      ORDER BY fecha_emision DESC
    `, [activos]);

    res.json(rows);
  } catch (error) {
    next(error);
  }
}

// Drill-down: CFDIs cancelados
export async function getCancelados(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const hace5 = new Date();
    hace5.setFullYear(hace5.getFullYear() - 5);

    const { rows } = await req.tenantPool!.query(`
      SELECT id, uuid, type, fecha_emision as "fechaEmision",
        rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor",
        rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor",
        total_mxn as "totalMxn", fecha_cancelacion as "fechaCancelacion"
      FROM cfdis
      WHERE status IN ('Cancelado', '0')
        AND (fecha_emision - interval '1 hour') >= $1::date
        ${cf}
      ORDER BY fecha_emision DESC
    `, [hace5.toISOString().split('T')[0]]);

    res.json(rows);
  } catch (error) {
    next(error);
  }
}

// Drill-down: Facturas de periodos anteriores canceladas este mes
export async function getCancelacionesPeriodoAnterior(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const ahora = new Date();
    const inicioMes = `${ahora.getFullYear()}-${String(ahora.getMonth() + 1).padStart(2, '0')}-01`;

    const { rows } = await req.tenantPool!.query(`
      SELECT id, uuid, type, fecha_emision as "fechaEmision",
        rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor",
        rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor",
        total_mxn as "totalMxn", tipo_comprobante as "tipoComprobante",
        fecha_cancelacion as "fechaCancelacion"
      FROM cfdis
      WHERE status IN ('Cancelado', '0')
        AND fecha_cancelacion >= $1::date
        AND COALESCE(fecha_efectiva, fecha_emision - interval '1 hour') < $1::date
        ${cf}
      ORDER BY fecha_cancelacion DESC
    `, [inicioMes]);

    res.json(rows);
  } catch (error) {
    next(error);
  }
}

// Drill-down: CFDIs con pago en efectivo
export async function getEfectivo(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const { rows } = await req.tenantPool!.query(`
      SELECT id, uuid, type, fecha_emision as "fechaEmision",
        rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor",
        rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor",
        total_mxn as "totalMxn", forma_pago as "formaPago"
      FROM cfdis
      WHERE status NOT IN ('Cancelado', '0') AND tipo_comprobante = 'I'
        AND forma_pago = '01'
        ${cf}
      ORDER BY fecha_emision DESC
    `);

    res.json(rows);
  } catch (error) {
    next(error);
  }
}

// Drill-down: CFDIs tipo E con TipoRelacion sospechoso (debería ser 07)
export async function getTipoRelacionSospechosa(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND c.contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    const { rows } = await req.tenantPool!.query(`
      SELECT c.id, c.uuid, c.type, c.fecha_emision AS "fechaEmision",
        c.rfc_emisor AS "rfcEmisor", c.nombre_emisor AS "nombreEmisor",
        c.rfc_receptor AS "rfcReceptor", c.nombre_receptor AS "nombreReceptor",
        c.total_mxn AS "totalMxn",
        c.tipo_comprobante AS "tipoComprobante",
        c.cfdi_tipo_relacion AS "cfdiTipoRelacion",
        c.cfdis_relacionados AS "cfdisRelacionados"
      FROM cfdis c
      WHERE ${SOSPECHOSA_TIPO_RELACION_WHERE_EXPORT}
      ${cf}
      ORDER BY c.fecha_emision DESC
    `);
    res.json(rows);
  } catch (error) {
    next(error);
  }
}

// ── Descarte de CFDIs de alertas ──

export async function descartarCfdis(req: Request, res: Response, next: NextFunction) {
  try {
    const { cfdiIds, tipoAlerta } = z.object({
      cfdiIds: z.array(z.number().int()),
      tipoAlerta: z.string().min(1),
    }).parse(req.body);

    for (const cfdiId of cfdiIds) {
      await req.tenantPool!.query(
        `INSERT INTO cfdi_descartados (cfdi_id, tipo_alerta, descartado_por)
         VALUES ($1, $2, $3) ON CONFLICT (cfdi_id, tipo_alerta) DO NOTHING`,
        [cfdiId, tipoAlerta, req.user!.email],
      );
    }
    res.json({ descartados: cfdiIds.length });
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function restaurarDescartados(req: Request, res: Response, next: NextFunction) {
  try {
    const { cfdiIds, tipoAlerta } = z.object({
      cfdiIds: z.array(z.number().int()).optional(),
      tipoAlerta: z.string().min(1),
    }).parse(req.body);

    if (cfdiIds && cfdiIds.length > 0) {
      await req.tenantPool!.query(
        `DELETE FROM cfdi_descartados WHERE tipo_alerta = $1 AND cfdi_id = ANY($2)`,
        [tipoAlerta, cfdiIds],
      );
    } else {
      await req.tenantPool!.query(
        `DELETE FROM cfdi_descartados WHERE tipo_alerta = $1`,
        [tipoAlerta],
      );
    }
    res.json({ success: true });
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function getDescartados(req: Request, res: Response, next: NextFunction) {
  try {
    const tipoAlerta = req.query.tipoAlerta as string;
    if (!tipoAlerta) return next(new AppError(400, 'tipoAlerta requerido'));
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const cf = contribuyenteId
      ? `AND c.contribuyente_id = '${contribuyenteId.replace(/[^a-f0-9-]/gi, '')}'`
      : '';

    // JOIN con cfdis para devolver datos completos (mismo shape que el
    // drill-down activo, para que el frontend pueda reutilizar el componente).
    const { rows } = await req.tenantPool!.query(`
      SELECT c.id, c.uuid, c.type, c.fecha_emision AS "fechaEmision",
        c.rfc_emisor AS "rfcEmisor", c.nombre_emisor AS "nombreEmisor",
        c.rfc_receptor AS "rfcReceptor", c.nombre_receptor AS "nombreReceptor",
        c.total_mxn AS "totalMxn",
        c.regimen_fiscal_receptor AS "regimenReceptor",
        d.descartado_por AS "descartadoPor",
        d.created_at AS "descartadoEn"
      FROM cfdi_descartados d
      JOIN cfdis c ON c.id = d.cfdi_id
      WHERE d.tipo_alerta = $1
        ${cf}
      ORDER BY d.created_at DESC
    `, [tipoAlerta]);
    res.json({ data: rows });
  } catch (error) { next(error); }
}
