import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { logAudit } from "../utils/auditLog.js";
import {
  buildPermissionGridState,
  canManageAdmin,
  fetchRolePermissions,
  getVisibleRoles,
  normalizePermissions,
  ROLE_LABELS,
  syncDefaultRoles,
} from "../utils/permissions.js";

const cleanString = (value) => String(value ?? "").trim();

const normalizeRoleName = (value) =>
  cleanString(value)
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "");

const mapRoleRow = (row) => {
  const permissions = normalizePermissions(row.permissions);

  return {
    id: Number(row.id),
    name: row.name,
    label: ROLE_LABELS[row.name] || row.name,
    permissions,
    permission_grid: buildPermissionGridState(permissions),
    users_count: Number(row.users_count || 0),
    visible: getVisibleRoles().some((role) => role.name === row.name),
  };
};

const ensureAdminPermission = async (db, req) => {
  await syncDefaultRoles(db);
  const permissions = await fetchRolePermissions(db, req.user?.role);

  if (!canManageAdmin(req.user?.role, permissions)) {
    throw new ApiError(403, "Accès refusé à la gestion des rôles.");
  }
};

export const rolesController = {
  list: async (req, res) => {
    const client = await pool.connect();

    try {
      await client.query("BEGIN");
      await ensureAdminPermission(client, req);

      const { rows } = await client.query(
        `
          SELECT
            r.id,
            r.name,
            r.permissions,
            COUNT(u.id)::int AS users_count
          FROM roles r
          LEFT JOIN users u
            ON u.role = r.name
          GROUP BY r.id, r.name, r.permissions
          ORDER BY r.name ASC
        `,
      );

      await client.query("COMMIT");

      res.json({
        items: rows
          .map(mapRoleRow)
          .sort((left, right) => {
            if (left.visible !== right.visible) {
              return left.visible ? -1 : 1;
            }

            return left.label.localeCompare(right.label, "fr");
          }),
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  create: async (req, res) => {
    const client = await pool.connect();

    try {
      await client.query("BEGIN");
      await ensureAdminPermission(client, req);

      const name = normalizeRoleName(req.body.name);
      const permissions = normalizePermissions(req.body.permissions);

      if (!name) {
        throw new ApiError(400, "Le nom du rôle est obligatoire.");
      }

      const { rows } = await client.query(
        `
          INSERT INTO roles (name, permissions)
          VALUES ($1, $2::jsonb)
          RETURNING id, name, permissions
        `,
        [name, JSON.stringify(permissions)],
      );

      const createdRole = mapRoleRow({
        ...rows[0],
        users_count: 0,
      });

      await logAudit(client, {
        companyId: null,
        userId: req.user?.sub || null,
        action: "roles.create",
        entityType: "role",
        entityId: createdRole.id,
        newData: createdRole,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.status(201).json({
        message: "Rôle créé.",
        item: createdRole,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  update: async (req, res) => {
    const client = await pool.connect();

    try {
      await client.query("BEGIN");
      await ensureAdminPermission(client, req);

      const roleId = Number(req.params.id);

      if (!roleId) {
        throw new ApiError(400, "Identifiant rôle invalide.");
      }

      const { rows: currentRows } = await client.query(
        `
          SELECT id, name, permissions
          FROM roles
          WHERE id = $1
          LIMIT 1
        `,
        [roleId],
      );

      if (!currentRows[0]) {
        throw new ApiError(404, "Rôle introuvable.");
      }

      const permissions = normalizePermissions(req.body.permissions);
      const previousRole = mapRoleRow({
        ...currentRows[0],
        users_count: 0,
      });

      const { rows } = await client.query(
        `
          UPDATE roles
          SET permissions = $2::jsonb
          WHERE id = $1
          RETURNING id, name, permissions
        `,
        [roleId, JSON.stringify(permissions)],
      );

      const updatedRole = mapRoleRow({
        ...rows[0],
        users_count: previousRole.users_count,
      });

      await logAudit(client, {
        companyId: null,
        userId: req.user?.sub || null,
        action: "roles.update",
        entityType: "role",
        entityId: roleId,
        oldData: previousRole,
        newData: updatedRole,
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        message: "Rôle mis à jour.",
        item: updatedRole,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },
};
