import { NextFunction, Request, Response } from 'express';
import { ResultSetHeader, RowDataPacket } from 'mysql2';
import { pool } from '../config/db.js';
import { MENUS } from '../constants/menus.js';
import { COMMON_MENU_KEY, getRolePermissions } from '../services/permissionService.js';

interface RoleRow extends RowDataPacket {
  id: number;
  name: string;
}

interface PermissionInput {
  menuKey: string;
  canCreate?: boolean;
  canRead?: boolean;
  canUpdate?: boolean;
  canDelete?: boolean;
}

function mysqlErrorCode(err: unknown): string | undefined {
  return err && typeof err === 'object' && 'code' in err ? (err as { code?: string }).code : undefined;
}

export async function list(_req: Request, res: Response, next: NextFunction) {
  try {
    const [rows] = await pool.query<RoleRow[]>('SELECT id, name FROM roles ORDER BY name');
    res.json(rows);
  } catch (err) {
    next(err);
  }
}

export async function create(req: Request, res: Response, next: NextFunction) {
  const { name } = req.body as { name?: string };
  if (!name?.trim()) {
    return res.status(400).json({ message: 'Role name is required' });
  }

  try {
    const [result] = await pool.query<ResultSetHeader>('INSERT INTO roles (name) VALUES (?)', [name.trim()]);
    res.status(201).json({ id: result.insertId, name: name.trim() });
  } catch (err: unknown) {
    if (mysqlErrorCode(err) === 'ER_DUP_ENTRY') {
      return res.status(409).json({ message: 'Role name already exists' });
    }
    next(err);
  }
}

export async function update(req: Request, res: Response, next: NextFunction) {
  const id = Number(req.params.id);
  const { name } = req.body as { name?: string };
  if (!name?.trim()) {
    return res.status(400).json({ message: 'Role name is required' });
  }

  try {
    const [result] = await pool.query<ResultSetHeader>('UPDATE roles SET name = ? WHERE id = ?', [name.trim(), id]);
    if (result.affectedRows === 0) {
      return res.status(404).json({ message: 'Role not found' });
    }
    res.json({ id, name: name.trim() });
  } catch (err: unknown) {
    if (mysqlErrorCode(err) === 'ER_DUP_ENTRY') {
      return res.status(409).json({ message: 'Role name already exists' });
    }
    next(err);
  }
}

export async function remove(req: Request, res: Response, next: NextFunction) {
  const id = Number(req.params.id);
  try {
    const [result] = await pool.query<ResultSetHeader>('DELETE FROM roles WHERE id = ?', [id]);
    if (result.affectedRows === 0) {
      return res.status(404).json({ message: 'Role not found' });
    }
    res.status(204).send();
  } catch (err: unknown) {
    const code = mysqlErrorCode(err);
    if (code === 'ER_ROW_IS_REFERENCED_2' || code === 'ER_ROW_IS_REFERENCED') {
      return res.status(409).json({ message: 'This role is assigned to users and cannot be deleted.' });
    }
    next(err);
  }
}

export async function getPermissions(req: Request, res: Response, next: NextFunction) {
  const roleId = Number(req.params.id);
  try {
    const [roleRows] = await pool.query<RoleRow[]>('SELECT id FROM roles WHERE id = ? LIMIT 1', [roleId]);
    if (!roleRows[0]) return res.status(404).json({ message: 'Role not found' });

    res.json(await getRolePermissions(roleId));
  } catch (err) {
    next(err);
  }
}

export async function updatePermissions(req: Request, res: Response, next: NextFunction) {
  const roleId = Number(req.params.id);
  const permissions = req.body as PermissionInput[];
  if (!Array.isArray(permissions)) {
    return res.status(400).json({ message: 'Permissions payload must be an array' });
  }

  const validKeys = new Set(MENUS.map((m) => m.key));

  try {
    const [roleRows] = await pool.query<RoleRow[]>('SELECT id FROM roles WHERE id = ? LIMIT 1', [roleId]);
    if (!roleRows[0]) return res.status(404).json({ message: 'Role not found' });

    for (const p of permissions) {
      if (!validKeys.has(p.menuKey) || p.menuKey === COMMON_MENU_KEY) continue;
      await pool.query(
        `INSERT INTO role_permissions (role_id, menu_key, can_create, can_read, can_update, can_delete)
         VALUES (?, ?, ?, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
           can_create = VALUES(can_create), can_read = VALUES(can_read),
           can_update = VALUES(can_update), can_delete = VALUES(can_delete)`,
        [roleId, p.menuKey, !!p.canCreate, !!p.canRead, !!p.canUpdate, !!p.canDelete],
      );
    }

    res.json(await getRolePermissions(roleId));
  } catch (err) {
    next(err);
  }
}
