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

// ── Asignación de obligaciones ──

export async function asignarObligacion(
  pool: Pool,
  obligacionId: string,
  auxiliarUserId: string,
  asignadoPor: string,
): Promise<void> {
  await pool.query(
    `INSERT INTO obligacion_asignaciones (obligacion_id, auxiliar_user_id, asignado_por)
     VALUES ($1, $2, $3)
     ON CONFLICT (obligacion_id)
     DO UPDATE SET auxiliar_user_id = $2, asignado_por = $3, asignado_at = now()`,
    [obligacionId, auxiliarUserId, asignadoPor],
  );
}

export async function desasignarObligacion(pool: Pool, obligacionId: string): Promise<void> {
  await pool.query('DELETE FROM obligacion_asignaciones WHERE obligacion_id = $1', [obligacionId]);
}

// ── Asignación de tareas ──

export async function asignarTarea(
  pool: Pool,
  tareaId: string,
  auxiliarUserId: string,
  asignadoPor: string,
): Promise<void> {
  await pool.query(
    `INSERT INTO tarea_asignaciones (tarea_id, auxiliar_user_id, asignado_por)
     VALUES ($1, $2, $3)
     ON CONFLICT (tarea_id)
     DO UPDATE SET auxiliar_user_id = $2, asignado_por = $3, asignado_at = now()`,
    [tareaId, auxiliarUserId, asignadoPor],
  );
}

export async function desasignarTarea(pool: Pool, tareaId: string): Promise<void> {
  await pool.query('DELETE FROM tarea_asignaciones WHERE tarea_id = $1', [tareaId]);
}

// ── Listados ──

export interface AsignacionObligacion {
  id: string;
  obligacionId: string;
  obligacionNombre: string;
  contribuyenteId: string;
  contribuyenteRfc: string;
  contribuyenteRazonSocial: string;
  auxiliarUserId: string;
  auxiliarNombre: string | null;
  asignadoPor: string;
  asignadoAt: string;
}

export interface AsignacionTarea {
  id: string;
  tareaId: string;
  tareaNombre: string;
  contribuyenteId: string;
  contribuyenteRfc: string;
  contribuyenteRazonSocial: string;
  auxiliarUserId: string;
  auxiliarNombre: string | null;
  asignadoPor: string;
  asignadoAt: string;
}

async function resolveUserNames(userIds: string[]): Promise<Map<string, string>> {
  const map = new Map<string, string>();
  if (userIds.length === 0) return map;
  const users = await prisma.user.findMany({
    where: { id: { in: userIds } },
    select: { id: true, nombre: true },
  });
  for (const u of users) {
    map.set(u.id, u.nombre);
  }
  return map;
}

/**
 * Devuelve todas las asignaciones de obligaciones y tareas de los auxiliares
 * que pertenecen al supervisor indicado (vía auxiliar_supervisores).
 * Owner ve todas las asignaciones del tenant.
 */
export async function getAsignacionesPorSupervisor(
  pool: Pool,
  supervisorUserId: string,
  role: string,
): Promise<{ obligaciones: AsignacionObligacion[]; tareas: AsignacionTarea[] }> {
  const isOwner = role === 'owner' || role === 'cfo' || role === 'contador';

  // Relación supervisor → auxiliar se infiere desde carteras (directas y
  // subcarteras) con fallback a la tabla legacy auxiliar_supervisores.
  const supervisorFilter = isOwner
    ? ''
    : `AND EXISTS (
         SELECT 1 FROM (
           SELECT c.auxiliar_user_id
             FROM carteras c
            WHERE c.supervisor_user_id = $1
              AND c.auxiliar_user_id IS NOT NULL
           UNION
           SELECT sub.auxiliar_user_id
             FROM carteras sub
             JOIN carteras p ON p.id = sub.parent_id
            WHERE p.supervisor_user_id = $1
              AND sub.auxiliar_user_id IS NOT NULL
           UNION
           SELECT auxiliar_user_id FROM auxiliar_supervisores WHERE supervisor_user_id = $1
         ) sup_aux WHERE sup_aux.auxiliar_user_id = __AUX_COL__
       )`;
  const whereObl = isOwner
    ? 'WHERE 1=1'
    : `WHERE 1=1 ${supervisorFilter.replace(/__AUX_COL__/g, 'oa.auxiliar_user_id')}`;
  const whereTarea = isOwner
    ? 'WHERE 1=1'
    : `WHERE 1=1 ${supervisorFilter.replace(/__AUX_COL__/g, 'ta.auxiliar_user_id')}`;
  const params = isOwner ? [] : [supervisorUserId];

  const { rows: obligaciones } = await pool.query<AsignacionObligacion>(
    `SELECT
       oa.id,
       oa.obligacion_id AS "obligacionId",
       oc.nombre AS "obligacionNombre",
       oc.contribuyente_id AS "contribuyenteId",
       c.rfc AS "contribuyenteRfc",
       COALESCE(r.razon_social, c.rfc) AS "contribuyenteRazonSocial",
       oa.auxiliar_user_id AS "auxiliarUserId",
       oa.asignado_por AS "asignadoPor",
       oa.asignado_at AS "asignadoAt"
     FROM obligacion_asignaciones oa
     JOIN obligaciones_contribuyente oc ON oc.id = oa.obligacion_id
     JOIN contribuyentes c ON c.entidad_id = oc.contribuyente_id
     LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc)
     ${whereObl}
     ORDER BY oa.asignado_at DESC`,
    params,
  );

  const { rows: tareas } = await pool.query<AsignacionTarea>(
    `SELECT
       ta.id,
       ta.tarea_id AS "tareaId",
       tc.nombre AS "tareaNombre",
       tc.contribuyente_id AS "contribuyenteId",
       c.rfc AS "contribuyenteRfc",
       COALESCE(r.razon_social, c.rfc) AS "contribuyenteRazonSocial",
       ta.auxiliar_user_id AS "auxiliarUserId",
       ta.asignado_por AS "asignadoPor",
       ta.asignado_at AS "asignadoAt"
     FROM tarea_asignaciones ta
     JOIN tareas_catalogo tc ON tc.id = ta.tarea_id
     JOIN contribuyentes c ON c.entidad_id = tc.contribuyente_id
     LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc)
     ${whereTarea}
     ORDER BY ta.asignado_at DESC`,
    params,
  );

  const allAuxIds = [...new Set([
    ...obligaciones.map(o => o.auxiliarUserId),
    ...tareas.map(t => t.auxiliarUserId),
  ])];
  const names = await resolveUserNames(allAuxIds);

  return {
    obligaciones: obligaciones.map(o => ({ ...o, auxiliarNombre: names.get(o.auxiliarUserId) ?? null })),
    tareas: tareas.map(t => ({ ...t, auxiliarNombre: names.get(t.auxiliarUserId) ?? null })),
  };
}

/**
 * Devuelve las asignaciones del auxiliar logueado.
 */
export async function getAsignacionesPorAuxiliar(
  pool: Pool,
  auxiliarUserId: string,
): Promise<{ obligaciones: AsignacionObligacion[]; tareas: AsignacionTarea[] }> {
  const { rows: obligaciones } = await pool.query<AsignacionObligacion>(
    `SELECT
       oa.id,
       oa.obligacion_id AS "obligacionId",
       oc.nombre AS "obligacionNombre",
       oc.contribuyente_id AS "contribuyenteId",
       c.rfc AS "contribuyenteRfc",
       COALESCE(r.razon_social, c.rfc) AS "contribuyenteRazonSocial",
       oa.auxiliar_user_id AS "auxiliarUserId",
       oa.asignado_por AS "asignadoPor",
       oa.asignado_at AS "asignadoAt"
     FROM obligacion_asignaciones oa
     JOIN obligaciones_contribuyente oc ON oc.id = oa.obligacion_id
     JOIN contribuyentes c ON c.entidad_id = oc.contribuyente_id
     LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc)
     WHERE oa.auxiliar_user_id = $1
     ORDER BY oa.asignado_at DESC`,
    [auxiliarUserId],
  );

  const { rows: tareas } = await pool.query<AsignacionTarea>(
    `SELECT
       ta.id,
       ta.tarea_id AS "tareaId",
       tc.nombre AS "tareaNombre",
       tc.contribuyente_id AS "contribuyenteId",
       c.rfc AS "contribuyenteRfc",
       COALESCE(r.razon_social, c.rfc) AS "contribuyenteRazonSocial",
       ta.auxiliar_user_id AS "auxiliarUserId",
       ta.asignado_por AS "asignadoPor",
       ta.asignado_at AS "asignadoAt"
     FROM tarea_asignaciones ta
     JOIN tareas_catalogo tc ON tc.id = ta.tarea_id
     JOIN contribuyentes c ON c.entidad_id = tc.contribuyente_id
     LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc)
     WHERE ta.auxiliar_user_id = $1
     ORDER BY ta.asignado_at DESC`,
    [auxiliarUserId],
  );

  const names = await resolveUserNames([auxiliarUserId]);
  const auxName = names.get(auxiliarUserId) ?? null;

  return {
    obligaciones: obligaciones.map(o => ({ ...o, auxiliarNombre: auxName })),
    tareas: tareas.map(t => ({ ...t, auxiliarNombre: auxName })),
  };
}

/**
 * Devuelve obligaciones activas sin asignar para los contribuyentes indicados.
 */
export async function getObligacionesSinAsignar(
  pool: Pool,
  entidadIds: string[],
): Promise<Omit<AsignacionObligacion, 'id' | 'auxiliarUserId' | 'auxiliarNombre' | 'asignadoPor' | 'asignadoAt'>[]> {
  if (entidadIds.length === 0) return [];
  const { rows } = await pool.query(
    `SELECT
       oc.id AS "obligacionId",
       oc.nombre AS "obligacionNombre",
       oc.contribuyente_id AS "contribuyenteId",
       c.rfc AS "contribuyenteRfc",
       COALESCE(r.razon_social, c.rfc) AS "contribuyenteRazonSocial"
     FROM obligaciones_contribuyente oc
     JOIN contribuyentes c ON c.entidad_id = oc.contribuyente_id
     LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc)
     LEFT JOIN obligacion_asignaciones oa ON oa.obligacion_id = oc.id
     WHERE oc.activa = true AND oa.id IS NULL AND oc.contribuyente_id = ANY($1)
     ORDER BY c.rfc, oc.nombre`,
    [entidadIds],
  );
  return rows;
}

/**
 * Devuelve tareas activas sin asignar para los contribuyentes indicados.
 */
export async function getTareasSinAsignar(
  pool: Pool,
  entidadIds: string[],
): Promise<Omit<AsignacionTarea, 'id' | 'auxiliarUserId' | 'auxiliarNombre' | 'asignadoPor' | 'asignadoAt'>[]> {
  if (entidadIds.length === 0) return [];
  const { rows } = await pool.query(
    `SELECT
       tc.id AS "tareaId",
       tc.nombre AS "tareaNombre",
       tc.contribuyente_id AS "contribuyenteId",
       c.rfc AS "contribuyenteRfc",
       COALESCE(r.razon_social, c.rfc) AS "contribuyenteRazonSocial"
     FROM tareas_catalogo tc
     JOIN contribuyentes c ON c.entidad_id = tc.contribuyente_id
     LEFT JOIN rfcs r ON UPPER(r.rfc) = UPPER(c.rfc)
     LEFT JOIN tarea_asignaciones ta ON ta.tarea_id = tc.id
     WHERE tc.active = true AND ta.id IS NULL AND tc.contribuyente_id = ANY($1)
     ORDER BY c.rfc, tc.nombre`,
    [entidadIds],
  );
  return rows;
}

/**
 * Resuelve el auxiliar asignado a una obligación (o null).
 */
export async function getAuxiliarAsignadoObligacion(
  pool: Pool,
  obligacionId: string,
): Promise<{ auxiliarUserId: string; auxiliarNombre: string | null } | null> {
  const { rows } = await pool.query<{ auxiliar_user_id: string }>(
    `SELECT oa.auxiliar_user_id
     FROM obligacion_asignaciones oa
     WHERE oa.obligacion_id = $1`,
    [obligacionId],
  );
  if (rows.length === 0) return null;
  const auxId = rows[0].auxiliar_user_id;
  const names = await resolveUserNames([auxId]);
  return { auxiliarUserId: auxId, auxiliarNombre: names.get(auxId) ?? null };
}

/**
 * Resuelve el auxiliar asignado a una tarea (o null).
 */
export async function getAuxiliarAsignadoTarea(
  pool: Pool,
  tareaId: string,
): Promise<{ auxiliarUserId: string; auxiliarNombre: string | null } | null> {
  const { rows } = await pool.query<{ auxiliar_user_id: string }>(
    `SELECT ta.auxiliar_user_id
     FROM tarea_asignaciones ta
     WHERE ta.tarea_id = $1`,
    [tareaId],
  );
  if (rows.length === 0) return null;
  const auxId = rows[0].auxiliar_user_id;
  const names = await resolveUserNames([auxId]);
  return { auxiliarUserId: auxId, auxiliarNombre: names.get(auxId) ?? null };
}

/**
 * Devuelve los userIds de auxiliares que tienen al contribuyente en alguna
 * de sus subcarteras (carteras con auxiliar_user_id no nulo que contienen
 * al contribuyente en cartera_entidades).
 */
export async function getAuxiliaresElegibles(
  pool: Pool,
  contribuyenteId: string,
): Promise<string[]> {
  const { rows } = await pool.query<{ auxiliar_user_id: string }>(
    `SELECT DISTINCT c.auxiliar_user_id
       FROM carteras c
       JOIN cartera_entidades ce ON ce.cartera_id = c.id
      WHERE ce.entidad_id = $1
        AND c.auxiliar_user_id IS NOT NULL`,
    [contribuyenteId],
  );
  return rows.map(r => r.auxiliar_user_id);
}
