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

export interface ContribuyentesStats {
  totalContribuyentes: number;
  ultimaExtraccion: Date | null;
  /** % global del despacho de obligaciones+tareas del periodo seleccionado completadas vs total. */
  progresoDelMes: number;
  /** Declaraciones cuyo `created_at` cae en el periodo seleccionado. */
  declaracionesPresentadas: number;
  /** Subset del anterior con `pdf_pago` no nulo. */
  declaracionesPagadas: number;
  /** Obligaciones de declaración pendientes de periodos anteriores al seleccionado. */
  declaracionesAtrasadas: number;
  /** Tareas pendientes con fecha_limite anterior al inicio del periodo seleccionado. */
  tareasAtrasadas: number;
}

/**
 * Métricas para la pestaña "Contribuyentes" del módulo Despacho (owner-only).
 *
 * Periodo: si se pasa `año`/`mes`, las métricas mensuales se calculan para
 * ese periodo. Default = mes en curso.
 *
 * - totalContribuyentes / ultimaExtraccion: independientes del periodo.
 * - progresoDelMes: % global obligaciones+tareas del periodo completadas.
 * - declaracionesPresentadas/pagadas: declaraciones con `created_at` en el
 *   periodo seleccionado (presentación, no devengo).
 * - declaracionesAtrasadas: obligaciones-periodo NO completadas con periodo
 *   anterior al seleccionado, donde la obligación sea de tipo declaración
 *   (categoría contiene 'mensual'/'anual'/'declaración' — heurística laxa
 *   ya que el catálogo no tiene un flag explícito).
 * - tareasAtrasadas: tareas-periodo NO completadas con fecha_limite anterior
 *   al primer día del periodo seleccionado.
 */
export async function getContribuyentesStats(
  pool: Pool,
  tenantId: string,
  año?: number,
  mes?: number,
): Promise<ContribuyentesStats> {
  const { rows: [{ count }] } = await pool.query<{ count: number }>(
    `SELECT COUNT(*)::int AS count
       FROM contribuyentes c
       JOIN entidades_gestionadas e ON e.id = c.entidad_id
      WHERE e.active = true`,
  );

  const last = await prisma.satSyncJob.findFirst({
    where: { tenantId, status: 'completed' },
    orderBy: { completedAt: 'desc' },
    select: { completedAt: true },
  });

  // Periodo: usa el filtrado o cae al mes en curso.
  const now = new Date();
  const _año = año ?? now.getFullYear();
  const _mes = mes ?? now.getMonth() + 1;
  const periodoMes = `${_año}-${String(_mes).padStart(2, '0')}`;
  const inicioMes = `${_año}-${String(_mes).padStart(2, '0')}-01`;
  const finMes = new Date(_año, _mes, 0).toISOString().split('T')[0];

  const { rows: [progresoRow] } = await pool.query<{ total: number; completadas: number }>(
    `SELECT
       (SELECT COUNT(*)::int FROM obligacion_periodos op
          JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id
         WHERE oc.activa = true AND op.periodo = $1)
     +
       (SELECT COUNT(*)::int FROM tarea_periodos tp
          JOIN tareas_catalogo tc ON tc.id = tp.tarea_id
         WHERE tc.active = true AND tp.fecha_limite BETWEEN $2::date AND $3::date)
       AS total,
       (SELECT COUNT(*)::int FROM obligacion_periodos op
          JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id
         WHERE oc.activa = true AND op.periodo = $1 AND op.completada = true)
     +
       (SELECT COUNT(*)::int FROM tarea_periodos tp
          JOIN tareas_catalogo tc ON tc.id = tp.tarea_id
         WHERE tc.active = true AND tp.fecha_limite BETWEEN $2::date AND $3::date AND tp.completada = true)
       AS completadas`,
    [periodoMes, inicioMes, finMes],
  );
  const progresoDelMes = progresoRow.total > 0
    ? Math.round((progresoRow.completadas / progresoRow.total) * 100)
    : 0;

  const { rows: [decRow] } = await pool.query<{ presentadas: number; pagadas: number }>(
    `SELECT
       COUNT(*)::int AS presentadas,
       COUNT(*) FILTER (WHERE pdf_pago IS NOT NULL)::int AS pagadas
       FROM declaraciones_provisionales
      WHERE created_at >= $1::date AND created_at < ($2::date + interval '1 day')`,
    [inicioMes, finMes],
  );

  // Atrasadas de periodos anteriores al seleccionado.
  // Para declaraciones (obligaciones) usamos `op.periodo < periodoMes`.
  // Heurística "es declaración": categoría contiene 'mensual', 'anual',
  // 'declaración' o el nombre incluye 'declaración' (case insensitive).
  const { rows: [atrRow] } = await pool.query<{ decl_atr: number; tar_atr: number }>(
    `SELECT
       (SELECT COUNT(*)::int FROM obligacion_periodos op
          JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id
         WHERE oc.activa = true
           AND op.completada = false
           AND op.periodo < $1
           AND (
             LOWER(COALESCE(oc.categoria, '')) ~ 'mensual|anual|declarac'
             OR LOWER(oc.nombre) LIKE '%declarac%'
           )
       ) AS decl_atr,
       (SELECT COUNT(*)::int FROM tarea_periodos tp
          JOIN tareas_catalogo tc ON tc.id = tp.tarea_id
         WHERE tc.active = true
           AND tp.completada = false
           AND tp.fecha_limite < $2::date
       ) AS tar_atr`,
    [periodoMes, inicioMes],
  );

  return {
    totalContribuyentes: count,
    ultimaExtraccion: last?.completedAt ?? null,
    progresoDelMes,
    declaracionesPresentadas: decRow.presentadas,
    declaracionesPagadas: decRow.pagadas,
    declaracionesAtrasadas: atrRow.decl_atr,
    tareasAtrasadas: atrRow.tar_atr,
  };
}

export interface ContribuyenteAsignado {
  contribuyenteId: string;
  rfc: string;
  nombre: string;
  carteraNombre: string | null;
  obligacionesPendientes: number;
  obligacionesAtrasadas: number;
  obligacionesCompletadas: number;
  tareasPendientes: number;
  tareasAtrasadas: number;
  tareasCompletadas: number;
}

/**
 * Resuelve los contribuyentes asignados al usuario actual según su rol y la
 * estructura de carteras:
 *
 * - **owner / cfo**: TODOS los contribuyentes del despacho.
 * - **supervisor**: contribuyentes que están en una cartera donde
 *   `c.supervisor_user_id = userId` o en una subcartera de tales carteras.
 * - **auxiliar**: contribuyentes en carteras donde `c.auxiliar_user_id = userId`.
 * - **otros (contador, cliente, etc.)**: vacío.
 *
 * Las métricas se calculan usando el periodo `año`/`mes` como pivote: lo
 * "atrasado" es lo NO completado de periodos anteriores al filtrado.
 */
export async function getMisAsignados(
  pool: Pool,
  userId: string,
  userRole: string,
  año?: number,
  mes?: number,
): Promise<ContribuyenteAsignado[]> {
  let baseFilter: string;
  const params: unknown[] = [];
  if (userRole === 'owner' || userRole === 'cfo') {
    baseFilter = `e.active = true`;
  } else if (userRole === 'supervisor') {
    params.push(userId);
    baseFilter = `e.active = true AND ce.cartera_id IN (
      SELECT id FROM carteras WHERE supervisor_user_id = $1
      UNION
      SELECT id FROM carteras WHERE parent_id IN (
        SELECT id FROM carteras WHERE supervisor_user_id = $1
      )
    )`;
  } else if (userRole === 'auxiliar') {
    params.push(userId);
    baseFilter = `e.active = true AND ce.cartera_id IN (
      SELECT id FROM carteras WHERE auxiliar_user_id = $1
    )`;
  } else {
    return [];
  }

  const { rows } = await pool.query(
    `SELECT DISTINCT c.entidad_id AS contribuyente_id, c.rfc, e.nombre,
            (SELECT cart.nombre FROM carteras cart
              JOIN cartera_entidades cee ON cee.cartera_id = cart.id
             WHERE cee.entidad_id = c.entidad_id LIMIT 1) AS cartera_nombre
       FROM contribuyentes c
       JOIN entidades_gestionadas e ON e.id = c.entidad_id
       LEFT JOIN cartera_entidades ce ON ce.entidad_id = c.entidad_id
      WHERE ${baseFilter}
      ORDER BY e.nombre`,
    params,
  );

  // Para cada contribuyente, contar pendientes/atrasados/completados de obligaciones+tareas.
  // Hacemos una sola query agregada por contribuyente con CTEs.
  const ids = rows.map(r => r.contribuyente_id);
  if (ids.length === 0) return [];

  // Periodo pivote: usa el filtrado o cae al mes en curso.
  const now = new Date();
  const _año = año ?? now.getFullYear();
  const _mes = mes ?? now.getMonth() + 1;
  const periodoMes = `${_año}-${String(_mes).padStart(2, '0')}`;
  const inicioMes = `${_año}-${String(_mes).padStart(2, '0')}-01`;
  const finMes = new Date(_año, _mes, 0).toISOString().split('T')[0];

  const { rows: stats } = await pool.query(
    `WITH obl AS (
       SELECT oc.contribuyente_id,
              COUNT(*) FILTER (WHERE op.completada = false AND op.periodo = $1)::int AS pendientes,
              COUNT(*) FILTER (WHERE op.completada = false AND op.periodo < $1)::int AS atrasadas,
              COUNT(*) FILTER (WHERE op.completada = true AND op.periodo = $1)::int AS completadas
         FROM obligaciones_contribuyente oc
         LEFT JOIN obligacion_periodos op ON op.obligacion_id = oc.id
        WHERE oc.contribuyente_id = ANY($4::uuid[]) AND oc.activa = true
        GROUP BY oc.contribuyente_id
     ),
     tar AS (
       SELECT tc.contribuyente_id,
              COUNT(*) FILTER (WHERE tp.completada = false AND tp.fecha_limite BETWEEN $2::date AND $3::date)::int AS pendientes,
              COUNT(*) FILTER (WHERE tp.completada = false AND tp.fecha_limite < $2::date)::int AS atrasadas,
              COUNT(*) FILTER (WHERE tp.completada = true AND tp.fecha_limite BETWEEN $2::date AND $3::date)::int AS completadas
         FROM tareas_catalogo tc
         LEFT JOIN tarea_periodos tp ON tp.tarea_id = tc.id
        WHERE tc.contribuyente_id = ANY($4::uuid[]) AND tc.active = true
        GROUP BY tc.contribuyente_id
     )
     SELECT
            obl.contribuyente_id AS obl_id, obl.pendientes AS obl_pen, obl.atrasadas AS obl_atr, obl.completadas AS obl_com,
            tar.contribuyente_id AS tar_id, tar.pendientes AS tar_pen, tar.atrasadas AS tar_atr, tar.completadas AS tar_com
       FROM obl
       FULL OUTER JOIN tar ON tar.contribuyente_id = obl.contribuyente_id`,
    [periodoMes, inicioMes, finMes, ids],
  );

  const statsMap = new Map<string, {
    obl: { pen: number; atr: number; com: number };
    tar: { pen: number; atr: number; com: number };
  }>();
  for (const s of stats) {
    const id = s.obl_id || s.tar_id;
    if (!id) continue;
    statsMap.set(id, {
      obl: { pen: s.obl_pen ?? 0, atr: s.obl_atr ?? 0, com: s.obl_com ?? 0 },
      tar: { pen: s.tar_pen ?? 0, atr: s.tar_atr ?? 0, com: s.tar_com ?? 0 },
    });
  }

  const result = rows.map(r => {
    const s = statsMap.get(r.contribuyente_id);
    return {
      contribuyenteId: r.contribuyente_id,
      rfc: r.rfc,
      nombre: r.nombre,
      carteraNombre: r.cartera_nombre,
      obligacionesPendientes: s?.obl.pen ?? 0,
      obligacionesAtrasadas: s?.obl.atr ?? 0,
      obligacionesCompletadas: s?.obl.com ?? 0,
      tareasPendientes: s?.tar.pen ?? 0,
      tareasAtrasadas: s?.tar.atr ?? 0,
      tareasCompletadas: s?.tar.com ?? 0,
    };
  });
  // Ordena por atrasos descendente — los más rezagados arriba.
  result.sort((a, b) => {
    const atrasoA = a.obligacionesAtrasadas + a.tareasAtrasadas;
    const atrasoB = b.obligacionesAtrasadas + b.tareasAtrasadas;
    if (atrasoA !== atrasoB) return atrasoB - atrasoA;
    return a.nombre.localeCompare(b.nombre);
  });
  return result;
}

export interface MiembroEquipo {
  userId: string;
  nombre: string;
  email: string;
  rol: 'supervisor' | 'auxiliar';
  contribuyentes: number;
  obligacionesAtrasadas: number;
  tareasAtrasadas: number;
  totalPendientes: number;
  /** completadas + pendientes del periodo filtrado (sin atrasos). */
  totalPeriodo: number;
  completadasPeriodo: number;
  avancePct: number | null;
}

export interface SupervisorConAuxiliares extends MiembroEquipo {
  auxiliares: MiembroEquipo[];
}

export interface EquipoStatsResponse {
  supervisores: SupervisorConAuxiliares[];
  /** Auxiliares activos sin entrada en `auxiliar_supervisores`. Solo owner los ve. */
  huerfanos: MiembroEquipo[];
}

/**
 * Resumen de avance por miembro del equipo, en estructura jerárquica
 * supervisor → auxiliares + lista de auxiliares "huérfanos" (sin supervisor).
 *
 * - **owner / cfo**: ve TODOS los supervisores + auxiliares sin supervisor.
 * - **supervisor**: ve solo a sí mismo con sus auxiliares (sin huérfanos).
 *
 * Métricas calculadas por periodo (`año`/`mes` o mes en curso).
 */
export async function getEquipoStats(
  pool: Pool,
  userId: string,
  userRole: string,
  tenantId: string,
  año?: number,
  mes?: number,
): Promise<EquipoStatsResponse> {
  // 1. Construir mapa supervisor → auxiliares.
  //
  // La relación se infiere desde `carteras`:
  // - Si una cartera tiene `auxiliar_user_id` Y `supervisor_user_id` no nulos,
  //   ese par directo cuenta.
  // - Si una subcartera (parent_id no nulo) tiene `auxiliar_user_id`, su
  //   supervisor es el `supervisor_user_id` del parent.
  //
  // Fallback: tabla legacy `auxiliar_supervisores`. La unión con DISTINCT
  // evita duplicados si un auxiliar aparece en ambas fuentes.
  const { rows: paresRows } = await pool.query<{ supervisor_user_id: string; auxiliar_user_id: string }>(
    `SELECT DISTINCT supervisor_user_id, auxiliar_user_id FROM (
       SELECT c.supervisor_user_id, c.auxiliar_user_id
         FROM carteras c
        WHERE c.auxiliar_user_id IS NOT NULL
          AND c.supervisor_user_id IS NOT NULL
       UNION
       SELECT p.supervisor_user_id, sub.auxiliar_user_id
         FROM carteras sub
         JOIN carteras p ON p.id = sub.parent_id
        WHERE sub.auxiliar_user_id IS NOT NULL
          AND p.supervisor_user_id IS NOT NULL
       UNION
       SELECT supervisor_user_id, auxiliar_user_id FROM auxiliar_supervisores
     ) t WHERE supervisor_user_id IS NOT NULL AND auxiliar_user_id IS NOT NULL`,
  );

  let pares = paresRows.map(r => ({ supervisorId: r.supervisor_user_id, auxiliarId: r.auxiliar_user_id }));
  if (userRole === 'supervisor') {
    pares = pares.filter(p => p.supervisorId === userId);
  } else if (userRole !== 'owner' && userRole !== 'cfo') {
    return { supervisores: [], huerfanos: [] };
  }

  // 2. Agrupar auxiliares por supervisor.
  const supervisorIds = [...new Set(pares.map(p => p.supervisorId))];
  const auxiliaresPorSup = new Map<string, string[]>();
  for (const p of pares) {
    if (!auxiliaresPorSup.has(p.supervisorId)) auxiliaresPorSup.set(p.supervisorId, []);
    auxiliaresPorSup.get(p.supervisorId)!.push(p.auxiliarId);
  }

  // 3. Para cada user (supervisor o auxiliar), calcular su miembro.
  const result: SupervisorConAuxiliares[] = [];
  for (const supId of supervisorIds) {
    const supMiembro = await calcularMiembro(pool, supId, 'supervisor', año, mes);
    if (!supMiembro) continue;
    const auxiliares: MiembroEquipo[] = [];
    for (const auxId of auxiliaresPorSup.get(supId) ?? []) {
      const auxMiembro = await calcularMiembro(pool, auxId, 'auxiliar', año, mes);
      if (auxMiembro) auxiliares.push(auxMiembro);
    }
    auxiliares.sort((a, b) => b.totalPendientes - a.totalPendientes);
    result.push({ ...supMiembro, auxiliares });
  }

  result.sort((a, b) => b.totalPendientes - a.totalPendientes);

  // 4. Auxiliares "huérfanos" (sin entrada en auxiliar_supervisores). Solo
  // el owner los ve para que pueda asignarles supervisor.
  let huerfanos: MiembroEquipo[] = [];
  if (userRole === 'owner' || userRole === 'cfo') {
    const auxiliaresMapeados = new Set(pares.map(p => p.auxiliarId));
    const auxiliaresActivos = await prisma.tenantMembership.findMany({
      where: { tenantId, active: true, rol: { nombre: 'auxiliar' } },
      include: { user: { select: { id: true, active: true } } },
    });
    const huerfanosIds = auxiliaresActivos
      .filter(m => m.user.active && !auxiliaresMapeados.has(m.userId))
      .map(m => m.userId);
    for (const auxId of huerfanosIds) {
      const aux = await calcularMiembro(pool, auxId, 'auxiliar', año, mes);
      if (aux) huerfanos.push(aux);
    }
    huerfanos.sort((a, b) => b.totalPendientes - a.totalPendientes);
  }

  return { supervisores: result, huerfanos };
}

async function calcularMiembro(
  pool: Pool,
  uId: string,
  rol: 'supervisor' | 'auxiliar',
  año?: number,
  mes?: number,
): Promise<MiembroEquipo | null> {
  const userInfo = await prisma.user.findUnique({
    where: { id: uId },
    select: { nombre: true, email: true, active: true },
  });
  if (!userInfo || !userInfo.active) return null;

  const filter = rol === 'supervisor'
    ? `ce.cartera_id IN (SELECT id FROM carteras WHERE supervisor_user_id = $1
         UNION SELECT id FROM carteras WHERE parent_id IN (SELECT id FROM carteras WHERE supervisor_user_id = $1))`
    : `ce.cartera_id IN (SELECT id FROM carteras WHERE auxiliar_user_id = $1)`;

  const { rows: contribRows } = await pool.query<{ entidad_id: string }>(
    `SELECT DISTINCT ce.entidad_id FROM cartera_entidades ce WHERE ${filter}`,
    [uId],
  );
  const contribIds = contribRows.map(r => r.entidad_id);

  // Periodo pivote
  const now = new Date();
  const _año = año ?? now.getFullYear();
  const _mes = mes ?? now.getMonth() + 1;
  const periodoMes = `${_año}-${String(_mes).padStart(2, '0')}`;
  const inicioMes = `${_año}-${String(_mes).padStart(2, '0')}-01`;
  const finMes = new Date(_año, _mes, 0).toISOString().split('T')[0];

  let obl = 0, tar = 0, total = 0, completadas = 0;
  if (contribIds.length > 0) {
    const { rows: [agg] } = await pool.query<{
      obl_atr: number; tar_atr: number;
      obl_pen: number; obl_com: number;
      tar_pen: number; tar_com: number;
    }>(
      `SELECT
         (SELECT COUNT(*)::int FROM obligacion_periodos op
            JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id
           WHERE oc.contribuyente_id = ANY($1::uuid[])
             AND oc.activa = true AND op.completada = false AND op.periodo < $2) AS obl_atr,
         (SELECT COUNT(*)::int FROM tarea_periodos tp
            JOIN tareas_catalogo tc ON tc.id = tp.tarea_id
           WHERE tc.contribuyente_id = ANY($1::uuid[])
             AND tc.active = true AND tp.completada = false AND tp.fecha_limite < $3::date) AS tar_atr,
         (SELECT COUNT(*)::int FROM obligacion_periodos op
            JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id
           WHERE oc.contribuyente_id = ANY($1::uuid[])
             AND oc.activa = true AND op.periodo = $2 AND op.completada = false) AS obl_pen,
         (SELECT COUNT(*)::int FROM obligacion_periodos op
            JOIN obligaciones_contribuyente oc ON oc.id = op.obligacion_id
           WHERE oc.contribuyente_id = ANY($1::uuid[])
             AND oc.activa = true AND op.periodo = $2 AND op.completada = true) AS obl_com,
         (SELECT COUNT(*)::int FROM tarea_periodos tp
            JOIN tareas_catalogo tc ON tc.id = tp.tarea_id
           WHERE tc.contribuyente_id = ANY($1::uuid[])
             AND tc.active = true AND tp.fecha_limite BETWEEN $3::date AND $4::date AND tp.completada = false) AS tar_pen,
         (SELECT COUNT(*)::int FROM tarea_periodos tp
            JOIN tareas_catalogo tc ON tc.id = tp.tarea_id
           WHERE tc.contribuyente_id = ANY($1::uuid[])
             AND tc.active = true AND tp.fecha_limite BETWEEN $3::date AND $4::date AND tp.completada = true) AS tar_com`,
      [contribIds, periodoMes, inicioMes, finMes],
    );
    obl = agg.obl_atr;
    tar = agg.tar_atr;
    completadas = agg.obl_com + agg.tar_com;
    total = completadas + agg.obl_pen + agg.tar_pen;
  }

  return {
    userId: uId,
    nombre: userInfo.nombre,
    email: userInfo.email,
    rol,
    contribuyentes: contribIds.length,
    obligacionesAtrasadas: obl,
    tareasAtrasadas: tar,
    totalPendientes: obl + tar,
    totalPeriodo: total,
    completadasPeriodo: completadas,
    avancePct: total > 0 ? Math.round((completadas / total) * 100) : null,
  };
}
