import { db } from "@/lib/db";
import { pagos, servicios, gastosServicios, pagosServicios } from "@/lib/schema";
import { desc, eq } from "drizzle-orm";
import { calcularResumen } from "@/lib/penalties";
import { calcularAjustesIpc } from "@/lib/ipc";
import { getContratoData, getIpcData, getAcuerdoVerbalFromDb } from "@/lib/data";
import { parseServicePaymentNotes } from "@/lib/service-payment-meta";

export const REPORT_TYPES = [
  "deuda_servicios_resumen",
  "deuda_servicios_detallada",
  "servicio_detalle",
  "deuda_alquiler_resumen",
  "alquiler_cuotas_impagas",
  "alquiler_mensual_completo",
  "pagos_alquiler_historial",
  "pagos_servicios_historial",
  "pagos_servicios_distribucion",
] as const;

export type ReportType = (typeof REPORT_TYPES)[number];

export interface TableReportPayload {
  title: string;
  watermarkTitle?: string;
  markdown: string;
}

interface GeneratedReport extends TableReportPayload {
  summary?: string;
}

const REPORT_PREFIX = "[[TABLE_REPORT:";
const REPORT_SUFFIX = "]]";

function formatARS(value: number): string {
  return `$${Math.round(value).toLocaleString("es-AR")}`;
}

function toMarkdownTable(headers: string[], rows: string[][]): string {
  const normalizedRows = rows.map((row) =>
    Array.from({ length: headers.length }, (_, index) => String(row[index] ?? "").trim())
  );

  return [
    `| ${headers.join(" | ")} |`,
    `| ${headers.map(() => "---").join(" | ")} |`,
    ...normalizedRows.map((row) => `| ${row.join(" | ")} |`),
  ].join("\n");
}

function wrapReport(report: GeneratedReport): string {
  const payload: TableReportPayload = {
    title: report.title,
    watermarkTitle: report.watermarkTitle ?? report.title,
    markdown: report.markdown,
  };

  const summary = report.summary?.trim();
  return `${summary ? `${summary}\n\n` : ""}${REPORT_PREFIX}${JSON.stringify(payload)}${REPORT_SUFFIX}`;
}

function formatPeriodo(periodo: string): string {
  return periodo;
}

function yesNo(value: boolean): string {
  return value ? "Si" : "No";
}

async function getRentResumen() {
  const hoy = new Date().toISOString().slice(0, 10);
  const [contrato, ipcData, acuerdo, pagoRows] = await Promise.all([
    getContratoData(),
    getIpcData(),
    getAcuerdoVerbalFromDb(),
    db.select().from(pagos).orderBy(desc(pagos.periodo)),
  ]);

  const pagosMap = new Map<string, { monto: number; fechaPago: string | null }>();
  for (const pago of pagoRows) {
    pagosMap.set(pago.periodo, { monto: pago.monto, fechaPago: pago.fechaPago ?? null });
  }

  const ajustes = calcularAjustesIpc(ipcData, contrato.calendario, contrato.montoBaseInicial);
  const resumen = calcularResumen(hoy, ajustes, contrato, pagosMap, acuerdo);

  return { hoy, resumen, pagoRows };
}

async function getServiciosContext() {
  const [serviciosData, gastosData, pagosServiciosData] = await Promise.all([
    db
      .select({ id: servicios.id, nombre: servicios.nombre, porcentaje: servicios.porcentaje })
      .from(servicios)
      .where(eq(servicios.activo, 1)),
    db.select().from(gastosServicios),
    db.select().from(pagosServicios).orderBy(desc(pagosServicios.fecha)),
  ]);

  return { serviciosData, gastosData, pagosServiciosData };
}

function matchServicio(nombreServicio: string, serviciosData: Array<{ id: number; nombre: string; porcentaje: number }>) {
  const normalized = nombreServicio.trim().toLowerCase();
  return serviciosData.find((servicio) => servicio.nombre.toLowerCase().includes(normalized));
}

async function buildDeudaServiciosResumenReport(): Promise<GeneratedReport> {
  const { serviciosData, gastosData } = await getServiciosContext();

  const rows: string[][] = [];
  let total = 0;

  for (const servicio of serviciosData) {
    const gastos = gastosData.filter((gasto) => gasto.servicioId === servicio.id);
    const facturado = gastos.reduce((sum, gasto) => sum + gasto.montoFacturado, 0);
    const pagado = gastos.reduce((sum, gasto) => sum + (gasto.montoPagado ?? 0), 0);
    const corresponde = facturado * (servicio.porcentaje / 100);
    const deuda = Math.round((corresponde - pagado) * 100) / 100;

    if (deuda <= 0.01) continue;

    total += deuda;
    rows.push([servicio.nombre, formatARS(deuda)]);
  }

  if (!rows.length) {
    rows.push(["Sin deuda pendiente", "$0"]);
  } else {
    rows.push(["Total", formatARS(total)]);
  }

  return {
    title: "Deuda de servicios",
    watermarkTitle: "Deuda de servicios",
    markdown: toMarkdownTable(["Servicio", "Deuda"], rows),
    summary: `La deuda total de servicios es ${formatARS(total)}.`,
  };
}

async function buildDeudaServiciosDetalladaReport(): Promise<GeneratedReport> {
  const { serviciosData, gastosData } = await getServiciosContext();

  const rows: string[][] = [];
  let total = 0;

  // Collect all per-period debts across all services
  for (const servicio of serviciosData) {
    const gastos = gastosData.filter((gasto) => gasto.servicioId === servicio.id);
    const pct = servicio.porcentaje / 100;

    for (const gasto of gastos) {
      const corresponde = Math.round(gasto.montoFacturado * pct * 100) / 100;
      const pagado = gasto.montoPagado ?? 0;
      const deuda = Math.round((corresponde - pagado) * 100) / 100;

      if (deuda <= 0.01) continue;

      total += deuda;
      rows.push([formatPeriodo(gasto.periodo), servicio.nombre, formatARS(deuda)]);
    }
  }

  // Sort by periodo descending (most recent first)
  rows.sort((a, b) => b[0].localeCompare(a[0]));

  if (!rows.length) {
    rows.push(["-", "Sin deuda pendiente", "$0"]);
  } else {
    rows.push(["", "Total", formatARS(total)]);
  }

  return {
    title: "Deuda de servicios por mes",
    watermarkTitle: "Deuda servicios mensual",
    markdown: toMarkdownTable(["Periodo", "Servicio", "Deuda"], rows),
    summary: `Desglose mensual de deuda por servicio. Total pendiente: ${formatARS(total)}.`,
  };
}

async function buildServicioDetalleReport(nombreServicio?: string): Promise<GeneratedReport> {
  if (!nombreServicio?.trim()) {
    throw new Error("Falta indicar el nombre del servicio.");
  }

  const { serviciosData, gastosData } = await getServiciosContext();
  const match = matchServicio(nombreServicio, serviciosData);
  if (!match) {
    throw new Error(`No se encontró el servicio "${nombreServicio}".`);
  }

  const rows = gastosData
    .filter((gasto) => gasto.servicioId === match.id)
    .sort((a, b) => b.periodo.localeCompare(a.periodo))
    .map((gasto) => {
      const corresponde = Math.round(gasto.montoFacturado * (match.porcentaje / 100) * 100) / 100;
      const pagado = gasto.montoPagado ?? 0;
      const deuda = Math.max(0, Math.round((corresponde - pagado) * 100) / 100);
      return [
        formatPeriodo(gasto.periodo),
        formatARS(gasto.montoFacturado),
        formatARS(corresponde),
        formatARS(pagado),
        formatARS(deuda),
        deuda <= 0 ? "Al dia" : "Pendiente",
      ];
    });

  if (!rows.length) {
    throw new Error(`No hay gastos registrados para ${match.nombre}.`);
  }

  return {
    title: `Detalle de ${match.nombre}`,
    watermarkTitle: `Detalle de ${match.nombre}`,
    markdown: toMarkdownTable(["Periodo", "Facturado", "Corresponde", "Pagado", "Deuda", "Estado"], rows),
    summary: `Te paso el detalle de ${match.nombre}.`,
  };
}

async function buildDeudaAlquilerResumenReport(): Promise<GeneratedReport> {
  const { resumen } = await getRentResumen();
  const total = resumen.deudaMensualImpaga + resumen.diferenciaAjustesIpc + resumen.totalPenalidad;

  return {
    title: "Resumen de deuda de alquiler",
    watermarkTitle: "Deuda de alquiler",
    markdown: toMarkdownTable(
      ["Concepto", "Monto"],
      [
        ["Impaga", formatARS(resumen.deudaMensualImpaga)],
        ["Diferencia IPC", formatARS(resumen.diferenciaAjustesIpc)],
        ["Penalidades", formatARS(resumen.totalPenalidad)],
        ["Total", formatARS(total)],
      ]
    ),
    summary: `La deuda total de alquiler es ${formatARS(total)}.`,
  };
}

async function buildAlquilerCuotasImpagasReport(): Promise<GeneratedReport> {
  const { resumen } = await getRentResumen();
  const rows = resumen.cuotas
    .filter((cuota) => cuota.montoEsperadoIpc - cuota.montoPagado > 0)
    .map((cuota) => [
      formatPeriodo(cuota.periodo),
      formatARS(cuota.montoEsperadoIpc),
      formatARS(cuota.montoPagado),
      formatARS(Math.max(0, cuota.montoEsperadoIpc - cuota.montoPagado)),
      String(cuota.diasMora),
      formatARS(cuota.penalidad),
    ]);

  if (!rows.length) {
    rows.push(["Sin cuotas pendientes", "$0", "$0", "$0", "0", "$0"]);
  }

  return {
    title: "Cuotas impagas de alquiler",
    watermarkTitle: "Cuotas impagas",
    markdown: toMarkdownTable(["Periodo", "Esperado IPC", "Pagado", "Saldo", "Dias mora", "Penalidad"], rows),
    summary: `Hay ${rows[0][0] === "Sin cuotas pendientes" ? 0 : rows.length} cuotas con saldo pendiente.`,
  };
}

async function buildAlquilerMensualCompletoReport(): Promise<GeneratedReport> {
  const { resumen } = await getRentResumen();

  const rows = resumen.cuotas.map((cuota) => [
    formatPeriodo(cuota.periodo),
    formatARS(cuota.montoEsperadoIpc),
    cuota.montoAcordado ? formatARS(cuota.montoAcordado) : "-",
    formatARS(cuota.montoPagado),
    formatARS(Math.max(0, cuota.diferenciaIpc)),
    String(cuota.diasMora),
    formatARS(cuota.penalidad),
    !cuota.pagado ? "Impago" : cuota.diferenciaIpc > 0 ? "Parcial" : "OK",
  ]);

  return {
    title: "Detalle mensual completo de alquiler",
    watermarkTitle: "Alquiler mensual",
    markdown: toMarkdownTable(["Periodo", "Esperado IPC", "Acordado", "Pagado", "Dif. IPC", "Dias mora", "Penalidad", "Estado"], rows),
    summary: `Te paso el detalle mensual completo del alquiler.`,
  };
}

async function buildPagosAlquilerHistorialReport(limit?: number): Promise<GeneratedReport> {
  const { pagoRows } = await getRentResumen();

  const rows = pagoRows
    .slice(0, limit && limit > 0 ? limit : undefined)
    .map((pago) => {
      const parsed = parseServicePaymentNotes(pago.notas);
      return [
        formatPeriodo(pago.periodo),
        pago.fechaPago ?? "-",
        formatARS(pago.monto),
        parsed.displayText || "-",
        pago.creadoPor || parsed.meta.uploader || "-",
        yesNo((parsed.meta.proofUrls?.length ?? 0) > 0),
      ];
    });

  if (!rows.length) {
    throw new Error("No hay pagos de alquiler registrados.");
  }

  const ultimo = rows[0];

  return {
    title: "Historial de pagos de alquiler",
    watermarkTitle: "Pagos de alquiler",
    markdown: toMarkdownTable(["Periodo", "Fecha pago", "Monto", "Notas", "Registrado por", "Comprobante"], rows),
    summary: `El ultimo pago de alquiler registrado fue ${ultimo[0]} por ${ultimo[2]} el ${ultimo[1]}.`,
  };
}

async function buildPagosServiciosHistorialReport(limit?: number): Promise<GeneratedReport> {
  const { serviciosData, pagosServiciosData } = await getServiciosContext();
  const serviceNameById = new Map(serviciosData.map((servicio) => [servicio.id, servicio.nombre]));

  const rows = pagosServiciosData
    .slice(0, limit && limit > 0 ? limit : undefined)
    .map((pago) => {
      const parsed = parseServicePaymentNotes(pago.notas);
      const specific = parsed.meta.paymentMode === "specific";
      const target = specific
        ? parsed.meta.serviceName || (parsed.meta.serviceId ? serviceNameById.get(parsed.meta.serviceId) : undefined) || "Servicio puntual"
        : "Todos";

      return [
        pago.fecha,
        formatARS(pago.monto),
        specific ? "Especifico" : "Global",
        target,
        pago.creadoPor || parsed.meta.uploader || "-",
        yesNo((parsed.meta.proofUrls?.length ?? 0) > 0),
      ];
    });

  if (!rows.length) {
    throw new Error("No hay pagos de servicios registrados.");
  }

  const ultimo = rows[0];

  return {
    title: "Historial de pagos de servicios",
    watermarkTitle: "Pagos de servicios",
    markdown: toMarkdownTable(["Fecha", "Monto", "Modo", "Servicio objetivo", "Registrado por", "Comprobante"], rows),
    summary: `El ultimo pago de servicios fue ${ultimo[1]} el ${ultimo[0]} (${ultimo[2].toLowerCase()}).`,
  };
}

async function buildPagosServiciosDistribucionReport(): Promise<GeneratedReport> {
  const { serviciosData, gastosData } = await getServiciosContext();

  const rows = serviciosData.map((servicio) => {
    const gastos = gastosData.filter((gasto) => gasto.servicioId === servicio.id);
    const corresponde = gastos.reduce((sum, gasto) => sum + gasto.montoFacturado * (servicio.porcentaje / 100), 0);
    const pagado = gastos.reduce((sum, gasto) => sum + (gasto.montoPagado ?? 0), 0);
    const deuda = Math.max(0, corresponde - pagado);

    return [servicio.nombre, formatARS(corresponde), formatARS(pagado), formatARS(deuda)];
  });

  return {
    title: "Distribucion de pagos de servicios",
    watermarkTitle: "Distribucion de servicios",
    markdown: toMarkdownTable(["Servicio", "Corresponde", "Pagado", "Deuda restante"], rows),
    summary: `Te paso la distribucion actual de pagos y deuda restante por servicio.`,
  };
}

export async function generarReporteTabla(tipo: ReportType, args: { nombre_servicio?: string; limite?: number }): Promise<string> {
  let report: GeneratedReport;

  switch (tipo) {
    case "deuda_servicios_resumen":
      report = await buildDeudaServiciosResumenReport();
      break;
    case "deuda_servicios_detallada":
      report = await buildDeudaServiciosDetalladaReport();
      break;
    case "servicio_detalle":
      report = await buildServicioDetalleReport(args.nombre_servicio);
      break;
    case "deuda_alquiler_resumen":
      report = await buildDeudaAlquilerResumenReport();
      break;
    case "alquiler_cuotas_impagas":
      report = await buildAlquilerCuotasImpagasReport();
      break;
    case "alquiler_mensual_completo":
      report = await buildAlquilerMensualCompletoReport();
      break;
    case "pagos_alquiler_historial":
      report = await buildPagosAlquilerHistorialReport(args.limite);
      break;
    case "pagos_servicios_historial":
      report = await buildPagosServiciosHistorialReport(args.limite);
      break;
    case "pagos_servicios_distribucion":
      report = await buildPagosServiciosDistribucionReport();
      break;
    default:
      throw new Error(`Tipo de reporte no soportado: ${tipo}`);
  }

  return wrapReport(report);
}
