import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import * as usuariosService from '../services/usuarios.service.js';
import { AppError } from '../middlewares/error.middleware.js';
import { isGlobalAdmin as checkGlobalAdmin } from '../utils/global-admin.js';

const inviteSchema = z.object({
  email: z.string().email('email inválido'),
  nombre: z.string().min(2).max(100),
  // Legacy Horux360 roles + Despacho-specific roles
  role: z.enum(['contador', 'visor', 'auxiliar', 'supervisor', 'cliente']),
  supervisorUserId: z.string().uuid().optional(), // Required when role=auxiliar
});

const updateSchema = z.object({
  nombre: z.string().min(2).max(100).optional(),
  // Legacy Horux360 roles + Despacho-specific roles
  role: z.enum(['contador', 'visor', 'auxiliar', 'supervisor', 'cliente']).optional(),
  active: z.boolean().optional(),
});

const updateGlobalSchema = z.object({
  nombre: z.string().min(2).max(100).optional(),
  role: z.enum(['owner', 'cfo', 'contador', 'visor', 'auxiliar', 'supervisor', 'cliente']).optional(),
  active: z.boolean().optional(),
  tenantId: z.string().uuid().optional(),
});

async function isGlobalAdmin(req: Request): Promise<boolean> {
  return checkGlobalAdmin(req.user!.tenantId, req.user!.role);
}

export async function getUsuarios(req: Request, res: Response, next: NextFunction) {
  try {
    const usuarios = await usuariosService.getUsuarios(req.user!.tenantId);
    res.json(usuarios);
  } catch (error) {
    next(error);
  }
}

/**
 * Obtiene todos los usuarios de todas las empresas (solo admin global)
 */
export async function getAllUsuarios(req: Request, res: Response, next: NextFunction) {
  try {
    if (!(await isGlobalAdmin(req))) {
      throw new AppError(403, 'Solo el administrador global puede ver todos los usuarios');
    }
    const usuarios = await usuariosService.getAllUsuarios();
    res.json(usuarios);
  } catch (error) {
    next(error);
  }
}

export async function inviteUsuario(req: Request, res: Response, next: NextFunction) {
  try {
    if (req.user!.role !== 'owner') {
      throw new AppError(403, 'Solo los dueños pueden invitar usuarios');
    }
    const data = inviteSchema.parse(req.body);

    // Validate: auxiliar requires a supervisor
    if (data.role === 'auxiliar' && !data.supervisorUserId) {
      throw new AppError(400, 'Debes asignar un supervisor al auxiliar');
    }

    const usuario = await usuariosService.inviteUsuario(req.user!.tenantId, data);

    // Store auxiliar→supervisor relationship in tenant DB
    if (data.role === 'auxiliar' && data.supervisorUserId && req.tenantPool) {
      await req.tenantPool.query(
        `INSERT INTO auxiliar_supervisores (auxiliar_user_id, supervisor_user_id)
         VALUES ($1, $2) ON CONFLICT (auxiliar_user_id) DO UPDATE SET supervisor_user_id = $2`,
        [usuario.id, data.supervisorUserId],
      );
    }

    res.status(201).json(usuario);
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function updateUsuario(req: Request, res: Response, next: NextFunction) {
  try {
    if (req.user!.role !== 'owner') {
      throw new AppError(403, 'Solo los dueños pueden modificar usuarios');
    }
    const userId = req.params.id as string;
    const data = updateSchema.parse(req.body);
    const usuario = await usuariosService.updateUsuario(req.user!.tenantId, userId, data);
    res.json(usuario);
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

/**
 * Lee el supervisor actualmente asignado a un auxiliar. Resuelve desde 3
 * fuentes (en orden de prioridad):
 *   1. `auxiliar_supervisores` (override explícito del owner desde /usuarios).
 *   2. Cartera donde el user es `auxiliar_user_id` y la misma tiene supervisor.
 *   3. Subcartera donde el user es `auxiliar_user_id`; el supervisor viene
 *      del cartera padre.
 *
 * Devuelve `null` si no aparece en ninguna.
 */
export async function getSupervisor(req: Request, res: Response, next: NextFunction) {
  try {
    if (!req.tenantPool) throw new AppError(500, 'Tenant pool no disponible');
    const userId = String(req.params.id);
    const { rows } = await req.tenantPool.query<{ supervisor_user_id: string }>(
      `SELECT supervisor_user_id FROM (
         SELECT supervisor_user_id, 1 AS prio FROM auxiliar_supervisores
          WHERE auxiliar_user_id = $1
         UNION ALL
         SELECT supervisor_user_id, 2 AS prio FROM carteras
          WHERE auxiliar_user_id = $1 AND supervisor_user_id IS NOT NULL
         UNION ALL
         SELECT p.supervisor_user_id, 3 AS prio
           FROM carteras sub
           JOIN carteras p ON p.id = sub.parent_id
          WHERE sub.auxiliar_user_id = $1 AND p.supervisor_user_id IS NOT NULL
       ) t
       WHERE supervisor_user_id IS NOT NULL
       ORDER BY prio
       LIMIT 1`,
      [userId],
    );
    res.json({ supervisorUserId: rows[0]?.supervisor_user_id ?? null });
  } catch (error) {
    next(error);
  }
}

const supervisorSchema = z.object({
  supervisorUserId: z.string().uuid().nullable(),
});

/**
 * Asigna o elimina el supervisor de un auxiliar (BD tenant).
 * Solo owner/cfo. Pasar `null` borra la asignación.
 */
export async function updateSupervisor(req: Request, res: Response, next: NextFunction) {
  try {
    if (req.user!.role !== 'owner' && req.user!.role !== 'cfo') {
      throw new AppError(403, 'Solo el owner puede asignar supervisores');
    }
    if (!req.tenantPool) throw new AppError(500, 'Tenant pool no disponible');
    const userId = String(req.params.id);
    const { supervisorUserId } = supervisorSchema.parse(req.body);

    if (supervisorUserId === null) {
      await req.tenantPool.query(
        `DELETE FROM auxiliar_supervisores WHERE auxiliar_user_id = $1`,
        [userId],
      );
    } else {
      await req.tenantPool.query(
        `INSERT INTO auxiliar_supervisores (auxiliar_user_id, supervisor_user_id)
         VALUES ($1, $2)
         ON CONFLICT (auxiliar_user_id) DO UPDATE SET supervisor_user_id = $2`,
        [userId, supervisorUserId],
      );
    }
    res.json({ supervisorUserId });
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

export async function deleteUsuario(req: Request, res: Response, next: NextFunction) {
  try {
    if (req.user!.role !== 'owner') {
      throw new AppError(403, 'Solo los dueños pueden eliminar usuarios');
    }
    const userId = req.params.id as string;
    if (userId === req.user!.userId) {
      throw new AppError(400, 'No puedes eliminar tu propia cuenta');
    }
    await usuariosService.deleteUsuario(req.user!.tenantId, userId);
    res.status(204).send();
  } catch (error) {
    next(error);
  }
}

/**
 * Actualiza un usuario globalmente (puede cambiar de empresa)
 */
export async function updateUsuarioGlobal(req: Request, res: Response, next: NextFunction) {
  try {
    if (!(await isGlobalAdmin(req))) {
      throw new AppError(403, 'Solo el administrador global puede modificar usuarios globalmente');
    }
    const userId = req.params.id as string;
    const data = updateGlobalSchema.parse(req.body);
    if (userId === req.user!.userId && data.tenantId) {
      throw new AppError(400, 'No puedes cambiar tu propia empresa');
    }
    const usuario = await usuariosService.updateUsuarioGlobal(userId, data);
    res.json(usuario);
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}

/**
 * Elimina un usuario globalmente
 */
export async function deleteUsuarioGlobal(req: Request, res: Response, next: NextFunction) {
  try {
    if (!(await isGlobalAdmin(req))) {
      throw new AppError(403, 'Solo el administrador global puede eliminar usuarios globalmente');
    }

    const userId = req.params.id as string;
    if (userId === req.user!.userId) {
      throw new AppError(400, 'No puedes eliminar tu propia cuenta');
    }
    await usuariosService.deleteUsuarioGlobal(userId);
    res.status(204).send();
  } catch (error) {
    next(error);
  }
}

/**
 * Get cliente accesos (which contribuyentes a client user can access)
 */
export async function getClienteAccesos(req: Request, res: Response, next: NextFunction) {
  try {
    if (req.user!.role !== 'owner') throw new AppError(403, 'No autorizado');
    const userId = req.params.id as string;
    const { rows } = await req.tenantPool!.query(
      'SELECT entidad_id AS "entidadId" FROM cliente_accesos WHERE user_id = $1',
      [userId],
    );
    res.json({ data: rows.map(r => r.entidadId) });
  } catch (error) { next(error); }
}

/**
 * Set cliente accesos (replace all accesos for a client user)
 */
export async function setClienteAccesos(req: Request, res: Response, next: NextFunction) {
  try {
    if (req.user!.role !== 'owner') throw new AppError(403, 'No autorizado');
    const userId = req.params.id as string;
    const { entidadIds } = z.object({
      entidadIds: z.array(z.string().uuid()),
    }).parse(req.body);

    // Replace all accesos
    await req.tenantPool!.query('DELETE FROM cliente_accesos WHERE user_id = $1', [userId]);
    for (const entidadId of entidadIds) {
      await req.tenantPool!.query(
        'INSERT INTO cliente_accesos (user_id, entidad_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
        [userId, entidadId],
      );
    }
    res.json({ data: entidadIds });
  } catch (error) {
    if (error instanceof z.ZodError) return next(new AppError(400, error.errors[0].message));
    next(error);
  }
}
