import { FastifyInstance } from "fastify";
import prisma from "../../config/database";
import { requirePermission } from "../../middleware/auth";
import { logAudit } from "../../services/audit";
import { success, error, parseId } from "../../utils/response";
import { parseBody } from "../../schemas/common";
import { CreateRoleSchema, UpdateRoleSchema } from "../../schemas/roles.schema";

export default async function rolesRoutes(
  fastify: FastifyInstance,
): Promise<void> {
  // GET /api/admin/roles
  fastify.get(
    "/",
    { preHandler: requirePermission("settings.roles") },
    async (request, reply) => {
      const [roles, userCounts] = await Promise.all([
        prisma.roles.findMany({
          include: {
            role_permissions: {
              include: { permissions: true },
            },
          },
          orderBy: { id: "asc" },
        }),
        prisma.users.groupBy({
          by: ["role_id"],
          _count: { id: true },
        }),
      ]);

      const countMap = new Map(
        userCounts.map((u) => [u.role_id, Number(u._count.id)]),
      );

      const data = roles.map((r) => ({
        ...r,
        permissions: r.role_permissions.map((rp) => rp.permissions),
        user_count: countMap.get(r.id) || 0,
      }));

      return success(reply, data);
    },
  );

  // GET /api/admin/roles/permissions
  fastify.get(
    "/permissions",
    { preHandler: requirePermission("settings.roles") },
    async (_request, reply) => {
      const permissions = await prisma.permissions.findMany({
        orderBy: [{ module: "asc" }, { id: "asc" }],
      });
      return success(reply, permissions);
    },
  );

  // POST /api/admin/roles
  fastify.post(
    "/",
    { preHandler: requirePermission("settings.roles") },
    async (request, reply) => {
      // Privilege-escalation guard: only an admin may create roles (and thus
      // attach arbitrary permission_ids). A non-admin `settings.roles` holder
      // must not be able to mint a new high-privilege role.
      if (request.authData!.roleName !== "admin") {
        return error(reply, "Pouze administrátor může vytvářet role", 403);
      }

      const parsed = parseBody(CreateRoleSchema, request.body);
      if ("error" in parsed) return error(reply, parsed.error, 400);
      const body = parsed.data;

      const name = String(body.name);

      // Never allow minting another "admin" role (would shadow / escalate).
      if (name.toLowerCase() === "admin") {
        return error(reply, "Nelze vytvořit roli s názvem admin", 400);
      }

      // Enforce role-name uniqueness explicitly (clear 409 instead of a raw
      // Prisma constraint 500).
      const duplicate = await prisma.roles.findFirst({ where: { name } });
      if (duplicate) {
        return error(reply, "Role s tímto názvem již existuje", 409);
      }

      // Role insert + permission assignment in a SINGLE transaction so a
      // partial failure can never leave an orphan role with no permissions.
      const role = await prisma.$transaction(async (tx) => {
        const created = await tx.roles.create({
          data: {
            name,
            display_name: String(body.display_name),
            description: body.description ? String(body.description) : null,
          },
        });

        if (Array.isArray(body.permission_ids) && body.permission_ids.length) {
          await tx.role_permissions.createMany({
            data: (body.permission_ids as number[]).map((pid) => ({
              role_id: created.id,
              permission_id: pid,
            })),
          });
        }

        return created;
      });

      await logAudit({
        request,
        authData: request.authData,
        action: "create",
        entityType: "role",
        entityId: role.id,
        description: `Vytvořena role ${role.name}`,
      });

      return success(reply, { id: role.id }, 201, "Role byla vytvořena");
    },
  );

  // PUT /api/admin/roles/:id
  fastify.put<{ Params: { id: string } }>(
    "/:id",
    { preHandler: requirePermission("settings.roles") },
    async (request, reply) => {
      // Privilege-escalation guard: only an admin may modify roles or their
      // permission sets.
      if (request.authData!.roleName !== "admin") {
        return error(reply, "Pouze administrátor může upravovat role", 403);
      }

      const id = parseId(request.params.id, reply);
      if (id === null) return;
      const parsed = parseBody(UpdateRoleSchema, request.body);
      if ("error" in parsed) return error(reply, parsed.error, 400);
      const body = parsed.data;

      const existing = await prisma.roles.findUnique({ where: { id } });
      if (!existing) return error(reply, "Role nenalezena", 404);

      // Mirror the DELETE guard: the built-in admin role's permission set is
      // immutable (it is resolved as "all permissions" anyway). Block any
      // attempt to rewrite it via permission_ids → no lockout / escalation.
      if (existing.name === "admin" && Array.isArray(body.permission_ids)) {
        return error(reply, "Oprávnění role admin nelze měnit", 400);
      }

      await prisma.roles.update({
        where: { id },
        data: {
          display_name: body.display_name
            ? String(body.display_name)
            : undefined,
          description:
            body.description !== undefined
              ? String(body.description)
              : undefined,
        },
      });

      if (Array.isArray(body.permission_ids)) {
        await prisma.$transaction([
          prisma.role_permissions.deleteMany({ where: { role_id: id } }),
          prisma.role_permissions.createMany({
            data: (body.permission_ids as number[]).map((pid) => ({
              role_id: id,
              permission_id: pid,
            })),
          }),
        ]);
      }

      await logAudit({
        request,
        authData: request.authData,
        action: "update",
        entityType: "role",
        entityId: id,
        description: `Upravena role ${existing.name}`,
      });

      return success(reply, { id }, 200, "Role byla aktualizována");
    },
  );

  // DELETE /api/admin/roles/:id
  fastify.delete<{ Params: { id: string } }>(
    "/:id",
    { preHandler: requirePermission("settings.roles") },
    async (request, reply) => {
      const id = parseId(request.params.id, reply);
      if (id === null) return;

      const existing = await prisma.roles.findUnique({ where: { id } });
      if (!existing) return error(reply, "Role nenalezena", 404);

      if (existing.name === "admin") {
        return error(reply, "Nelze smazat roli admin", 400);
      }

      await prisma.roles.delete({ where: { id } });

      await logAudit({
        request,
        authData: request.authData,
        action: "delete",
        entityType: "role",
        entityId: id,
        description: `Smazána role ${existing.name}`,
      });

      return success(reply, { id }, 200, "Role byla smazána");
    },
  );
}
