import type { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { AppError } from '../middlewares/error.middleware.js';
import * as tareasService from '../services/tareas.service.js';
import { getEntidadesVisibles } from '../utils/entidades-visibles.js';
import { emailService } from '../services/email/email.service.js';
import { getUserEmailById } from '../utils/memberships.js';
import { env } from '../config/env.js';
import { prisma } from '../config/database.js';

/**
 * Bloquea a usuarios rol `cliente` de cualquier endpoint de tareas.
 * El cliente no debe ver tareas operativas internas del despacho.
 */
function rejectClienteRole(req: Request): void {
  if (req.user?.role === 'cliente') {
    throw new AppError(403, 'Tareas no disponibles para usuarios cliente');
  }
}

const RECURRENCIAS = ['semanal', 'quincenal', 'mensual', 'bimestral', 'trimestral', 'semestral', 'anual'] as const;

const tareaSchema = z.object({
  nombre: z.string().min(1).max(200),
  descripcion: z.string().max(1000).nullable().optional(),
  recurrencia: z.enum(RECURRENCIAS),
  diaSemana: z.number().int().min(1).max(7).nullable().optional(),
  diaMes: z.number().int().min(1).max(31).nullable().optional(),
  soloSupervisorCompleta: z.boolean().optional(),
  orden: z.number().int().optional(),
});

export async function listTareas(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    if (!contribuyenteId) return next(new AppError(400, 'contribuyenteId requerido'));
    const tareas = await tareasService.listTareasConPeriodoActual(req.tenantPool!, contribuyenteId);
    res.json(tareas);
  } catch (error) {
    next(error);
  }
}

export async function createTarea(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    if (!contribuyenteId) return next(new AppError(400, 'contribuyenteId requerido'));
    const data = tareaSchema.parse(req.body);
    const tarea = await tareasService.createTarea(req.tenantPool!, contribuyenteId, data);
    res.status(201).json(tarea);
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function updateTarea(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const data = tareaSchema.partial().parse(req.body);
    const updated = await tareasService.updateTarea(req.tenantPool!, String(req.params.id), data);
    if (!updated) return next(new AppError(404, 'Tarea no encontrada'));
    res.json(updated);
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function deleteTarea(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const ok = await tareasService.deleteTarea(req.tenantPool!, String(req.params.id));
    if (!ok) return next(new AppError(404, 'Tarea no encontrada'));
    res.status(204).send();
  } catch (error) {
    next(error);
  }
}

export async function completarPeriodo(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const { notas } = z.object({ notas: z.string().max(1000).nullable().optional() }).parse(req.body);
    const result = await tareasService.completarPeriodo(
      req.tenantPool!,
      String(req.params.id),
      req.user!.userId,
      req.user!.role,
      notas ?? null,
    );
    if (!result) return next(new AppError(404, 'Periodo no encontrado'));

    // Notificar al auxiliar de la cartera SOLO cuando una tarea con
    // solo_supervisor_completa=true fue marcada como completada por
    // un supervisor/owner. Fire-and-forget — no bloquea la respuesta.
    if (result.tarea.soloSupervisorCompleta) {
      notifyAuxiliarTareaCompletada(req, result).catch(err =>
        console.error('[tareas.completar] notify auxiliar failed:', err?.message || err),
      );
    }
    res.json(result);
  } catch (error: any) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    if (error?.message?.startsWith('Solo supervisor')) return next(new AppError(403, error.message));
    next(error);
  }
}

async function notifyAuxiliarTareaCompletada(
  req: Request,
  result: { periodo: tareasService.TareaPeriodo; tarea: tareasService.TareaCatalogo },
): Promise<void> {
  const auxiliarUserId = await tareasService.getAuxiliarUserIdDeContribuyente(
    req.tenantPool!,
    result.tarea.contribuyenteId,
  );
  if (!auxiliarUserId) return;
  if (auxiliarUserId === req.user!.userId) return; // no notificarse a sí mismo
  const auxiliarEmail = await getUserEmailById(auxiliarUserId);
  if (!auxiliarEmail) return;

  // Datos del contribuyente y supervisor para el email
  const { rows } = await req.tenantPool!.query<{ rfc: string; nombre: string }>(
    `SELECT c.rfc, eg.nombre
       FROM contribuyentes c
       JOIN entidades_gestionadas eg ON eg.id = c.entidad_id
      WHERE c.entidad_id = $1`,
    [result.tarea.contribuyenteId],
  );
  if (rows.length === 0) return;

  const auxiliarNombre = (await prisma.user.findUnique({
    where: { id: auxiliarUserId },
    select: { nombre: true },
  }))?.nombre || 'Auxiliar';

  const fechaLimite = result.periodo.fechaLimite instanceof Date
    ? result.periodo.fechaLimite.toLocaleDateString('es-MX', { dateStyle: 'long' })
    : new Date(String(result.periodo.fechaLimite)).toLocaleDateString('es-MX', { dateStyle: 'long' });

  await emailService.sendTareaCompletada(auxiliarEmail, {
    destinatarioNombre: auxiliarNombre,
    contribuyenteNombre: rows[0].nombre,
    contribuyenteRfc: rows[0].rfc,
    tareaNombre: result.tarea.nombre,
    tareaDescripcion: result.tarea.descripcion,
    completadaPor: req.user!.email,
    notas: result.periodo.notas,
    fechaLimite,
    link: `${env.FRONTEND_URL}/configuracion/obligaciones`,
  });
}

export async function descompletarPeriodo(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const ok = await tareasService.descompletarPeriodo(req.tenantPool!, String(req.params.id));
    if (!ok) return next(new AppError(404, 'Periodo no encontrado'));
    res.status(204).send();
  } catch (error) {
    next(error);
  }
}

export async function listMisTareas(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const entidadIds = await getEntidadesVisibles(req.tenantPool!, req.user!.userId, req.user!.role);
    const tareas = await tareasService.listTareasConPeriodoPorContribuyentes(req.tenantPool!, entidadIds);
    res.json(tareas);
  } catch (error) {
    next(error);
  }
}

export async function seedDefaults(req: Request, res: Response, next: NextFunction) {
  try {
    rejectClienteRole(req);
    const contribuyenteId = req.query.contribuyenteId as string | undefined;
    if (!contribuyenteId) return next(new AppError(400, 'contribuyenteId requerido'));
    const created = await tareasService.seedTareasDefault(req.tenantPool!, contribuyenteId);
    res.json({ created });
  } catch (error) {
    next(error);
  }
}
