import type { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import * as asignacionesService from '../services/asignaciones.service.js';
import { getEntidadesVisibles } from '../utils/entidades-visibles.js';
import { AppError } from '../middlewares/error.middleware.js';

/**
 * Valida que el auxiliar pertenezca al supervisor (o que el caller sea owner).
 * Owner puede asignar a cualquier auxiliar del tenant.
 * La relación se infiere desde carteras (directas y subcarteras) con fallback
 * a la tabla legacy auxiliar_supervisores.
 */
async function validarAuxiliarDelSupervisor(
  pool: import('pg').Pool,
  supervisorUserId: string,
  auxiliarUserId: string,
  callerRole: string,
): Promise<void> {
  if (callerRole === 'owner') return;

  const { rows } = await pool.query(
    `SELECT 1 FROM (
       SELECT c.auxiliar_user_id
         FROM carteras c
        WHERE c.supervisor_user_id = $1
          AND c.auxiliar_user_id = $2
       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 = $2
       UNION
       SELECT auxiliar_user_id FROM auxiliar_supervisores
        WHERE supervisor_user_id = $1 AND auxiliar_user_id = $2
     ) t LIMIT 1`,
    [supervisorUserId, auxiliarUserId],
  );

  if (rows.length === 0) {
    throw new AppError(403, 'El auxiliar no pertenece a tu equipo');
  }
}

/**
 * Valida que el auxiliar tenga al contribuyente en alguna de sus subcarteras.
 * Si no hay ningún auxiliar con ese contribuyente en su subcartera, la asignación
 * se rechaza (el supervisor debe agregar el contribuyente a una subcartera primero).
 */
async function validarAuxiliarEnSubcartera(
  pool: import('pg').Pool,
  contribuyenteId: string,
  auxiliarUserId: string,
): Promise<void> {
  const elegibles = await asignacionesService.getAuxiliaresElegibles(pool, contribuyenteId);
  if (elegibles.length === 0) {
    throw new AppError(403, 'Ningún auxiliar tiene este contribuyente en su subcartera');
  }
  if (!elegibles.includes(auxiliarUserId)) {
    throw new AppError(403, 'El auxiliar no tiene este contribuyente en ninguna de sus subcarteras');
  }
}

// ── Obligaciones ──

export async function asignarObligacion(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = String(req.params.id);
    const obligacionId = String(req.params.obligacionId);
    const schema = z.object({ auxiliarUserId: z.string().uuid() });
    const { auxiliarUserId } = schema.parse(req.body);

    await validarAuxiliarDelSupervisor(
      req.tenantPool!,
      req.user!.userId,
      auxiliarUserId,
      req.user!.role,
    );
    await validarAuxiliarEnSubcartera(
      req.tenantPool!,
      contribuyenteId,
      auxiliarUserId,
    );

    await asignacionesService.asignarObligacion(
      req.tenantPool!,
      obligacionId,
      auxiliarUserId,
      req.user!.userId,
    );

    res.json({ message: 'Obligación asignada' });
  } catch (err: any) {
    if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
    next(err);
  }
}

export async function desasignarObligacion(req: Request, res: Response, next: NextFunction) {
  try {
    const obligacionId = String(req.params.obligacionId);
    await asignacionesService.desasignarObligacion(req.tenantPool!, obligacionId);
    res.json({ message: 'Asignación de obligación eliminada' });
  } catch (err) { next(err); }
}

// ── Tareas ──

export async function asignarTarea(req: Request, res: Response, next: NextFunction) {
  try {
    const tareaId = String(req.params.id);
    const schema = z.object({ auxiliarUserId: z.string().uuid() });
    const { auxiliarUserId } = schema.parse(req.body);

    await validarAuxiliarDelSupervisor(
      req.tenantPool!,
      req.user!.userId,
      auxiliarUserId,
      req.user!.role,
    );

    // Obtener contribuyenteId de la tarea para validar subcartera
    const { rows } = await req.tenantPool!.query<{ contribuyente_id: string }>(
      `SELECT contribuyente_id FROM tareas_catalogo WHERE id = $1 LIMIT 1`,
      [tareaId],
    );
    if (rows.length > 0) {
      await validarAuxiliarEnSubcartera(
        req.tenantPool!,
        rows[0].contribuyente_id,
        auxiliarUserId,
      );
    }

    await asignacionesService.asignarTarea(
      req.tenantPool!,
      tareaId,
      auxiliarUserId,
      req.user!.userId,
    );

    res.json({ message: 'Tarea asignada' });
  } catch (err: any) {
    if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
    next(err);
  }
}

export async function desasignarTarea(req: Request, res: Response, next: NextFunction) {
  try {
    const tareaId = String(req.params.id);
    await asignacionesService.desasignarTarea(req.tenantPool!, tareaId);
    res.json({ message: 'Asignación de tarea eliminada' });
  } catch (err) { next(err); }
}

// ── Listados ──

export async function listPorSupervisor(req: Request, res: Response, next: NextFunction) {
  try {
    const data = await asignacionesService.getAsignacionesPorSupervisor(
      req.tenantPool!,
      req.user!.userId,
      req.user!.role,
    );
    res.json(data);
  } catch (err) { next(err); }
}

export async function listPorAuxiliar(req: Request, res: Response, next: NextFunction) {
  try {
    const data = await asignacionesService.getAsignacionesPorAuxiliar(
      req.tenantPool!,
      req.user!.userId,
    );
    res.json(data);
  } catch (err) { next(err); }
}

export async function listSinAsignar(req: Request, res: Response, next: NextFunction) {
  try {
    const entidadIds = await getEntidadesVisibles(req.tenantPool!, req.user!.userId, req.user!.role);
    const [obligaciones, tareas] = await Promise.all([
      asignacionesService.getObligacionesSinAsignar(req.tenantPool!, entidadIds),
      asignacionesService.getTareasSinAsignar(req.tenantPool!, entidadIds),
    ]);
    res.json({ obligaciones, tareas });
  } catch (err) { next(err); }
}

export async function listAuxiliaresElegibles(req: Request, res: Response, next: NextFunction) {
  try {
    const contribuyenteId = String(req.params.contribuyenteId);
    const auxIds = await asignacionesService.getAuxiliaresElegibles(req.tenantPool!, contribuyenteId);
    res.json({ auxiliares: auxIds });
  } catch (err) { next(err); }
}
