import type { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { getOpiniones, getOpinionPdf, consultarOpinion, consultarOpinionContribuyente } from '../services/opinion-cumplimiento.service.js';
import * as declaracionesService from '../services/declaraciones.service.js';
import * as constanciaService from '../services/constancia.service.js';
import * as extrasService from '../services/documentos-extras.service.js';
import * as obligacionEvidenciasService from '../services/obligacion-evidencias.service.js';
import { notifyDocumentoSubido } from '../services/notify-upload.service.js';
import { AppError } from '../middlewares/error.middleware.js';

const MESES = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];

function effectiveTenantId(req: Request): string {
  return req.viewingTenantId || req.user!.tenantId;
}

export async function listarOpiniones(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    let rfc: string | undefined;
    if (contribuyenteId) {
      const { rows } = await req.tenantPool!.query(
        'SELECT rfc FROM contribuyentes WHERE entidad_id = $1',
        [contribuyenteId],
      );
      rfc = rows[0]?.rfc;
    }
    const opiniones = await getOpiniones(req.tenantPool!, 5, rfc);
    res.json(opiniones);
  } catch (error) {
    next(error);
  }
}

export async function descargarPdf(req: Request, res: Response, next: NextFunction) {
  try {
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return res.status(400).json({ error: 'ID inválido' });

    const pdf = await getOpinionPdf(req.tenantPool!, id);
    if (!pdf) return res.status(404).json({ error: 'Opinión no encontrada' });

    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="opinion_cumplimiento_${id}.pdf"`);
    res.send(pdf);
  } catch (error) {
    next(error);
  }
}

export async function consultarManual(req: Request, res: Response, next: NextFunction) {
  try {
    const tenantId = effectiveTenantId(req);
    const contribuyenteId = req.query.contribuyenteId as string | undefined;

    let opinion;
    if (contribuyenteId) {
      opinion = await consultarOpinionContribuyente(req.tenantPool!, contribuyenteId);
    } else {
      opinion = await consultarOpinion(tenantId);
    }
    res.json(opinion);
  } catch (error: any) {
    if (error.message?.includes('FIEL')) {
      return res.status(400).json({ error: error.message });
    }
    next(error);
  }
}

// ============================================================================
// Declaraciones provisionales
// ============================================================================

const ROLES_UPLOAD = ['owner', 'cfo', 'contador', 'auxiliar', 'supervisor'];

function canUpload(req: Request): boolean {
  return ROLES_UPLOAD.includes(req.user!.role);
}

const createDeclaracionSchema = z.object({
  año: z.number().int().min(2020).max(2100),
  mes: z.number().int().min(1).max(12),
  tipo: z.enum(['normal', 'complementaria']),
  periodicidad: z.enum(['mensual', 'bimestral', 'trimestral', 'cuatrimestral', 'semestral', 'anual']).optional(),
  impuestos: z.array(z.enum(['IVA', 'ISR', 'IEPS', 'ISN', 'DIOT', 'OTRO', 'ISH'])).optional(),
  obligacionesIds: z.array(z.string().uuid()).optional(),
  montoPago: z.number().min(0).optional(),
  pdfBase64: z.string().min(100),
  pdfFilename: z.string().min(1).max(255),
  ligaPagoBase64: z.string().min(100).optional(),
  ligaPagoFilename: z.string().min(1).max(255).optional(),
  notas: z.string().max(2000).optional(),
}).refine(
  d => !d.ligaPagoBase64 || !!d.ligaPagoFilename,
  { message: 'Si incluyes liga de pago, también debes mandar su nombre de archivo', path: ['ligaPagoFilename'] },
).refine(
  d => (d.obligacionesIds && d.obligacionesIds.length > 0) || (d.impuestos && d.impuestos.length > 0),
  { message: 'Selecciona al menos una obligación fiscal o un impuesto', path: ['obligacionesIds'] },
);

export async function listarDeclaraciones(req: Request, res: Response, next: NextFunction) {
  try {
    const fechaDesde = req.query.fechaDesde as string | undefined;
    const fechaHasta = req.query.fechaHasta as string | undefined;
    const contribuyenteId = typeof req.query.contribuyenteId === 'string' && req.query.contribuyenteId
      ? req.query.contribuyenteId
      : null;
    const data = await declaracionesService.listDeclaraciones(req.tenantPool!, fechaDesde, fechaHasta, contribuyenteId);
    res.json(data);
  } catch (error) { next(error); }
}

export async function crearDeclaracion(req: Request, res: Response, next: NextFunction) {
  try {
    if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para subir declaraciones' });
    const data = createDeclaracionSchema.parse(req.body);
    const contribuyenteId = req.body.contribuyenteId as string | undefined;
    const result = await declaracionesService.createDeclaracion(req.tenantPool!, {
      ...data,
      creadoPor: req.user!.email,
      creadoPorUserId: req.user!.userId,
      contribuyenteId,
    });

    // Notificación fire-and-forget a owners del despacho + supervisor del RFC.
    // Incluye como adjuntos el acuse de declaración y la liga de pago (si se subió).
    // No bloquea la respuesta ni falla la creación si SMTP no está configurado.
    notifyDocumentoSubido({
      pool: req.tenantPool!,
      tenantId: req.viewingTenantId ?? req.user!.tenantId,
      contribuyenteId: contribuyenteId ?? null,
      subidoPor: req.user!.email,
      kind: 'declaracion',
      declaracionId: result.declaracion.id,
      declaracion: {
        periodo: `${MESES[data.mes - 1]} ${data.año}`,
        tipo: data.tipo,
        impuestos: data.impuestos as string[],
        montoPago: data.montoPago ?? null,
      },
    }).catch((err: any) => console.error('[notifyDocumentoSubido declaracion]', err?.message || err));

    res.status(201).json(result);
  } catch (error: any) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    if (error?.message?.includes('Ya existe') || error?.message?.includes('normal')) {
      return next(new AppError(400, error.message));
    }
    next(error);
  }
}

const comprobantePagoSchema = z.object({
  pdfBase64: z.string().min(100),
  pdfFilename: z.string().min(1).max(255),
});

export async function subirComprobantePago(req: Request, res: Response, next: NextFunction) {
  try {
    if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para subir comprobantes' });
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    const data = comprobantePagoSchema.parse(req.body);
    const result = await declaracionesService.uploadComprobantePago(req.tenantPool!, id, {
      ...data,
      uploadedByUserId: req.user!.userId,
    });
    res.json(result);
  } catch (error: any) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    if (error?.message?.includes('no encontrada')) {
      return next(new AppError(404, error.message));
    }
    next(error);
  }
}

export async function descargarDeclaracionPdf(req: Request, res: Response, next: NextFunction) {
  try {
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    const v = req.params.variant;
    const variant: 'declaracion' | 'liga' | 'pago' = v === 'pago' ? 'pago' : v === 'liga' ? 'liga' : 'declaracion';
    const pdf = await declaracionesService.getDeclaracionPdf(req.tenantPool!, id, variant);
    if (!pdf) return res.status(404).json({ message: 'PDF no encontrado' });
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="${pdf.filename}"`);
    res.send(pdf.buffer);
  } catch (error) { next(error); }
}

// ============================================================================
// Constancia de Situación Fiscal
// ============================================================================

export async function listarConstancias(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    let rfc: string | undefined;
    if (contribuyenteId) {
      const { rows } = await req.tenantPool!.query(
        'SELECT rfc FROM contribuyentes WHERE entidad_id = $1',
        [contribuyenteId],
      );
      rfc = rows[0]?.rfc;
    }
    const data = await constanciaService.listConstancias(req.tenantPool!, 12, rfc);
    res.json(data);
  } catch (error) { next(error); }
}

export async function descargarConstanciaPdf(req: Request, res: Response, next: NextFunction) {
  try {
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    const pdf = await constanciaService.getConstanciaPdf(req.tenantPool!, id);
    if (!pdf) return res.status(404).json({ message: 'Constancia no encontrada' });
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="constancia_${id}.pdf"`);
    res.send(pdf);
  } catch (error) { next(error); }
}

export async function consultarConstanciaManual(req: Request, res: Response, next: NextFunction) {
  try {
    const tenantId = effectiveTenantId(req);
    const contribuyenteId = req.query.contribuyenteId as string | undefined;

    let constancia;
    if (contribuyenteId) {
      constancia = await constanciaService.consultarConstanciaContribuyente(req.tenantPool!, contribuyenteId);
    } else {
      constancia = await constanciaService.consultarConstancia(tenantId);
    }
    res.json(constancia);
  } catch (error: any) {
    if (error.message?.includes('FIEL')) return res.status(400).json({ error: error.message });
    if (error.message?.includes('Timeout') || error.name === 'TimeoutError') {
      return res.status(504).json({ error: 'El portal del SAT no respondió a tiempo. Intenta de nuevo en unos minutos.' });
    }
    next(error);
  }
}

export async function eliminarDeclaracion(req: Request, res: Response, next: NextFunction) {
  try {
    if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para eliminar declaraciones' });
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    await declaracionesService.deleteDeclaracion(req.tenantPool!, id);
    res.status(204).send();
  } catch (error: any) {
    if (error?.message?.includes('no encontrada')) {
      return next(new AppError(404, error.message));
    }
    next(error);
  }
}

// ============================================================================
// Documentos Extras — PDFs libres (acuses, contratos, poderes, estados, etc.)
// ============================================================================

const createExtraSchema = z.object({
  nombre: z.string().min(1, 'Nombre requerido').max(255),
  descripcion: z.string().max(2000).optional(),
  categoria: z.string().max(100).optional(),
  pdfBase64: z.string().min(100, 'PDF requerido'),
  pdfFilename: z.string().min(1).max(255),
});

export async function listarExtras(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const categoria = req.query.categoria as string | undefined;
    const data = await extrasService.listExtras(req.tenantPool!, contribuyenteId, categoria);
    res.json(data);
  } catch (error) { next(error); }
}

export async function crearExtra(req: Request, res: Response, next: NextFunction) {
  try {
    if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para subir documentos' });
    const data = createExtraSchema.parse(req.body);
    const contribuyenteId = req.body.contribuyenteId as string | undefined;
    const result = await extrasService.createExtra(req.tenantPool!, {
      ...data,
      contribuyenteId: contribuyenteId ?? null,
      subidoPor: req.user!.email,
    });

    // Notificación fire-and-forget a owners del despacho + supervisor del RFC.
    notifyDocumentoSubido({
      pool: req.tenantPool!,
      tenantId: req.viewingTenantId ?? req.user!.tenantId,
      contribuyenteId: contribuyenteId ?? null,
      subidoPor: req.user!.email,
      kind: 'extra',
      extra: {
        nombre: data.nombre,
        descripcion: data.descripcion ?? null,
        categoria: data.categoria ?? null,
      },
    }).catch((err: any) => console.error('[notifyDocumentoSubido extra]', err?.message || err));

    res.status(201).json(result);
  } catch (error: any) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function descargarExtraPdf(req: Request, res: Response, next: NextFunction) {
  try {
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    const pdf = await extrasService.getExtraPdf(req.tenantPool!, id);
    if (!pdf) return next(new AppError(404, 'Documento no encontrado'));
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="${pdf.filename}"`);
    res.send(pdf.buffer);
  } catch (error) { next(error); }
}

export async function eliminarExtra(req: Request, res: Response, next: NextFunction) {
  try {
    if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para eliminar documentos' });
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    const ok = await extrasService.deleteExtra(req.tenantPool!, id);
    if (!ok) return next(new AppError(404, 'Documento no encontrado'));
    res.status(204).send();
  } catch (error) { next(error); }
}

export async function listarCategoriasExtras(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    const data = await extrasService.listCategorias(req.tenantPool!, contribuyenteId);
    res.json(data);
  } catch (error) { next(error); }
}

// ═══════════════════════════════════════════════════════════════════════════
// Obligación evidencias — documentos que cierran obligaciones fiscales
// ═══════════════════════════════════════════════════════════════════════════

const createEvidenciaObligacionSchema = z.object({
  contribuyenteId: z.string().uuid('contribuyenteId inválido'),
  obligacionId: z.string().uuid('obligacionId inválido'),
  periodo: z.string().regex(/^\d{4}-\d{2}$/, 'periodo debe ser YYYY-MM'),
  tipoDocumento: z.enum(['declaracion', 'pago', 'acuse', 'complemento']),
  pdfBase64: z.string().min(100, 'PDF requerido'),
  pdfFilename: z.string().min(1).max(255),
  notas: z.string().max(2000).optional(),
});

export async function listarEvidenciasObligacion(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    if (!contribuyenteId) return next(new AppError(400, 'contribuyenteId requerido'));
    const periodo = req.query.periodo as string | undefined;
    const obligacionId = req.query.obligacionId as string | undefined;
    const data = await obligacionEvidenciasService.listEvidencias(req.tenantPool!, contribuyenteId, {
      periodo,
      obligacionId,
    });
    res.json(data);
  } catch (error) { next(error); }
}

export async function crearEvidenciaObligacion(req: Request, res: Response, next: NextFunction) {
  try {
    if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para subir documentos' });
    const data = createEvidenciaObligacionSchema.parse(req.body);
    const result = await obligacionEvidenciasService.createEvidencia(req.tenantPool!, {
      ...data,
      subidoPor: req.user!.userId,
      subidoPorEmail: req.user!.email,
    });

    // Notificación fire-and-forget a owners + supervisor del contribuyente.
    const { rows: obRows } = await req.tenantPool!.query<{ nombre: string }>(
      'SELECT nombre FROM obligaciones_contribuyente WHERE id = $1',
      [data.obligacionId],
    );
    notifyDocumentoSubido({
      pool: req.tenantPool!,
      tenantId: req.viewingTenantId ?? req.user!.tenantId,
      contribuyenteId: data.contribuyenteId,
      subidoPor: req.user!.email,
      kind: 'obligacion_evidencia',
      evidencia: {
        obligacionNombre: obRows[0]?.nombre || 'Obligación fiscal',
        periodo: data.periodo,
        tipoDocumento: data.tipoDocumento,
        filename: data.pdfFilename,
      },
      pdfBase64: data.pdfBase64,
    }).catch((err: any) => console.error('[notifyDocumentoSubido obligacion_evidencia]', err?.message || err));

    res.status(201).json(result);
  } catch (error: any) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function descargarEvidenciaObligacion(req: Request, res: Response, next: NextFunction) {
  try {
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    const pdf = await obligacionEvidenciasService.getEvidenciaPdf(req.tenantPool!, id);
    if (!pdf) return next(new AppError(404, 'Evidencia no encontrada'));
    res.setHeader('Content-Type', pdf.mime);
    res.setHeader('Content-Disposition', `attachment; filename="${pdf.filename}"`);
    res.send(pdf.buffer);
  } catch (error) { next(error); }
}

export async function eliminarEvidenciaObligacion(req: Request, res: Response, next: NextFunction) {
  try {
    if (!canUpload(req)) return res.status(403).json({ message: 'No tienes permiso para eliminar documentos' });
    const id = parseInt(String(req.params.id));
    if (isNaN(id)) return next(new AppError(400, 'id inválido'));
    const result = await obligacionEvidenciasService.deleteEvidencia(req.tenantPool!, id);
    if (!result) return next(new AppError(404, 'Evidencia no encontrada'));
    res.status(204).send();
  } catch (error) { next(error); }
}
