import express from "express";
import { z } from "zod";
import { authenticate, requireMfa } from "../../middleware/auth.js";
import { requirePermission } from "../../middleware/rbac.js";
import { validate } from "../../middleware/validate.js";
import { withDbSession } from "../../config/db.js";
import { getUserAccess } from "../../services/accessControl.js";
import { writeAudit } from "../../utils/audit.js";

const router = express.Router();
router.use(authenticate);

const roleSchema = z.object({
  code: z.string().min(2).max(80).regex(/^[a-z0-9_.-]+$/i),
  name: z.string().min(2).max(120),
  description: z.string().max(2000).optional().nullable(),
  is_active: z.boolean().optional().default(true),
});

const teamRoleSchema = z.object({
  code: z.string().min(2).max(80).regex(/^[a-z0-9_.-]+$/i),
  name: z.string().min(2).max(120),
  description: z.string().max(2000).optional().nullable(),
  can_view_cases: z.boolean().optional().default(false),
  can_write_cases: z.boolean().optional().default(false),
  can_view_documents: z.boolean().optional().default(false),
  can_write_documents: z.boolean().optional().default(false),
  is_active: z.boolean().optional().default(true),
});

const matrixSchema = z.object({
  page_keys: z.array(z.string().min(1)).optional().default([]),
  permission_keys: z.array(z.string().min(1)).optional().default([]),
});

const assignmentSchema = z.object({
  role_profile_ids: z.array(z.coerce.number().int().positive()).optional().default([]),
  team_role_ids: z.array(z.coerce.number().int().positive()).optional().default([]),
});

async function getMatrix(db) {
  const [pages, permissions, roles, rolePages, rolePermissions, teamRoles, users, userRoles, userTeamRoles] = await Promise.all([
    db.query(`SELECT page_key, path, label, nav_group, sort_order, description FROM app_page ORDER BY sort_order, label`),
    db.query(`SELECT permission_key, label, category, description FROM app_permission ORDER BY category, label`),
    db.query(`SELECT id, code, name, description, system_role, is_system, is_active FROM app_role_profile ORDER BY is_system DESC, name`),
    db.query(`SELECT role_profile_id, page_key FROM app_role_page WHERE can_view = TRUE`),
    db.query(`SELECT role_profile_id, permission_key FROM app_role_permission WHERE granted = TRUE`),
    db.query(`SELECT id, code, name, description, can_view_cases, can_write_cases, can_view_documents, can_write_documents, is_active FROM app_team_role ORDER BY name`),
    db.query(`SELECT id, full_name, email, role, committee_id, is_active FROM app_user ORDER BY full_name`),
    db.query(`SELECT user_id, role_profile_id FROM app_user_role_profile`),
    db.query(`SELECT user_id, team_role_id, committee_id FROM app_user_team_role`),
  ]);

  const pagesByRole = new Map();
  for (const row of rolePages.rows) {
    if (!pagesByRole.has(String(row.role_profile_id))) pagesByRole.set(String(row.role_profile_id), []);
    pagesByRole.get(String(row.role_profile_id)).push(row.page_key);
  }

  const permissionsByRole = new Map();
  for (const row of rolePermissions.rows) {
    if (!permissionsByRole.has(String(row.role_profile_id))) permissionsByRole.set(String(row.role_profile_id), []);
    permissionsByRole.get(String(row.role_profile_id)).push(row.permission_key);
  }

  return {
    pages: pages.rows,
    permissions: permissions.rows,
    role_profiles: roles.rows.map((role) => ({
      ...role,
      page_keys: pagesByRole.get(String(role.id)) || [],
      permission_keys: permissionsByRole.get(String(role.id)) || [],
    })),
    team_roles: teamRoles.rows,
    users: users.rows.map((user) => ({
      ...user,
      role_profile_ids: userRoles.rows.filter((row) => Number(row.user_id) === Number(user.id)).map((row) => row.role_profile_id),
      team_role_ids: userTeamRoles.rows.filter((row) => Number(row.user_id) === Number(user.id)).map((row) => row.team_role_id),
      team_role_assignments: userTeamRoles.rows.filter((row) => Number(row.user_id) === Number(user.id)),
    })),
  };
}

router.get("/access", async (req, res, next) => {
  try {
    const access = await getUserAccess(req.user.sub, req.user.role);
    res.json(access);
  } catch (err) {
    next(err);
  }
});

router.get("/matrix", requirePermission("system.permissions", ["admin"]), async (req, res, next) => {
  try {
    const data = await withDbSession(req.user, getMatrix);
    res.json(data);
  } catch (err) {
    next(err);
  }
});

router.post("/roles", requireMfa, requirePermission("system.permissions", ["admin"]), validate(roleSchema), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `INSERT INTO app_role_profile(code, name, description, is_system, is_active, created_by, updated_by)
         VALUES ($1,$2,$3,FALSE,$4,$5,$5)
         RETURNING id, code, name, description, system_role, is_system, is_active`,
        [req.body.code, req.body.name, req.body.description || null, req.body.is_active, req.user.sub]
      );
      return result.rows[0];
    });
    await writeAudit({ actorId: req.user.sub, action: "ROLE_PROFILE_CREATED", entityType: "role_profile", entityId: row.id, metadata: req.body, ipAddress: req.ip });
    res.status(201).json(row);
  } catch (err) {
    next(err);
  }
});

router.put("/roles/:id", requireMfa, requirePermission("system.permissions", ["admin"]), validate(roleSchema.partial()), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const current = await db.query(`SELECT id, is_system FROM app_role_profile WHERE id = $1`, [req.params.id]);
      if (!current.rows[0]) return null;
      const result = await db.query(
        `UPDATE app_role_profile
            SET code = COALESCE($2, code),
                name = COALESCE($3, name),
                description = COALESCE($4, description),
                is_active = COALESCE($5, is_active),
                updated_by = $6,
                updated_at = NOW()
          WHERE id = $1
          RETURNING id, code, name, description, system_role, is_system, is_active`,
        [req.params.id, req.body.code, req.body.name, req.body.description, req.body.is_active, req.user.sub]
      );
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Rolle nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "ROLE_PROFILE_UPDATED", entityType: "role_profile", entityId: row.id, metadata: req.body, ipAddress: req.ip });
    res.json(row);
  } catch (err) {
    next(err);
  }
});

router.put("/roles/:id/matrix", requireMfa, requirePermission("system.permissions", ["admin"]), validate(matrixSchema), async (req, res, next) => {
  try {
    const role = await withDbSession(req.user, async (db) => {
      const existing = await db.query(`SELECT id, is_system FROM app_role_profile WHERE id = $1`, [req.params.id]);
      if (!existing.rows[0]) return null;

      await db.query(`DELETE FROM app_role_page WHERE role_profile_id = $1`, [req.params.id]);
      if (req.body.page_keys.length) {
        await db.query(
          `INSERT INTO app_role_page(role_profile_id, page_key, can_view)
           SELECT $1::bigint, unnest($2::text[]), TRUE
           ON CONFLICT (role_profile_id, page_key) DO UPDATE SET can_view = TRUE`,
          [req.params.id, req.body.page_keys]
        );
      }

      await db.query(`DELETE FROM app_role_permission WHERE role_profile_id = $1`, [req.params.id]);
      if (req.body.permission_keys.length) {
        await db.query(
          `INSERT INTO app_role_permission(role_profile_id, permission_key, granted)
           SELECT $1::bigint, unnest($2::text[]), TRUE
           ON CONFLICT (role_profile_id, permission_key) DO UPDATE SET granted = TRUE`,
          [req.params.id, req.body.permission_keys]
        );
      }

      return existing.rows[0];
    });

    if (!role) return res.status(404).json({ error: "Rolle nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "ROLE_MATRIX_UPDATED", entityType: "role_profile", entityId: Number(req.params.id), metadata: req.body, ipAddress: req.ip });
    const data = await withDbSession(req.user, getMatrix);
    res.json(data);
  } catch (err) {
    next(err);
  }
});

router.post("/team-roles", requireMfa, requirePermission("system.permissions", ["admin"]), validate(teamRoleSchema), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `INSERT INTO app_team_role(code, name, description, can_view_cases, can_write_cases, can_view_documents, can_write_documents, is_active, created_by, updated_by)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$9)
         RETURNING *`,
        [req.body.code, req.body.name, req.body.description || null, req.body.can_view_cases, req.body.can_write_cases, req.body.can_view_documents, req.body.can_write_documents, req.body.is_active, req.user.sub]
      );
      return result.rows[0];
    });
    await writeAudit({ actorId: req.user.sub, action: "TEAM_ROLE_CREATED", entityType: "team_role", entityId: row.id, metadata: req.body, ipAddress: req.ip });
    res.status(201).json(row);
  } catch (err) {
    next(err);
  }
});

router.put("/team-roles/:id", requireMfa, requirePermission("system.permissions", ["admin"]), validate(teamRoleSchema.partial()), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `UPDATE app_team_role
            SET code = COALESCE($2, code),
                name = COALESCE($3, name),
                description = COALESCE($4, description),
                can_view_cases = COALESCE($5, can_view_cases),
                can_write_cases = COALESCE($6, can_write_cases),
                can_view_documents = COALESCE($7, can_view_documents),
                can_write_documents = COALESCE($8, can_write_documents),
                is_active = COALESCE($9, is_active),
                updated_by = $10,
                updated_at = NOW()
          WHERE id = $1
          RETURNING *`,
        [req.params.id, req.body.code, req.body.name, req.body.description, req.body.can_view_cases, req.body.can_write_cases, req.body.can_view_documents, req.body.can_write_documents, req.body.is_active, req.user.sub]
      );
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Teamrolle nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "TEAM_ROLE_UPDATED", entityType: "team_role", entityId: row.id, metadata: req.body, ipAddress: req.ip });
    res.json(row);
  } catch (err) {
    next(err);
  }
});

router.put("/users/:id/assignments", requireMfa, requirePermission("system.permissions", ["admin"]), validate(assignmentSchema), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const user = await db.query(`SELECT id FROM app_user WHERE id = $1`, [req.params.id]);
      if (!user.rows[0]) return null;

      await db.query(`DELETE FROM app_user_role_profile WHERE user_id = $1`, [req.params.id]);
      if (req.body.role_profile_ids.length) {
        await db.query(
          `INSERT INTO app_user_role_profile(user_id, role_profile_id, assigned_by)
           SELECT $1::bigint, unnest($2::bigint[]), $3::bigint
           ON CONFLICT (user_id, role_profile_id) DO NOTHING`,
          [req.params.id, req.body.role_profile_ids, req.user.sub]
        );
      }

      await db.query(`DELETE FROM app_user_team_role WHERE user_id = $1`, [req.params.id]);
      if (req.body.team_role_ids.length) {
        await db.query(
          `INSERT INTO app_user_team_role(user_id, team_role_id, committee_id, assigned_by)
           SELECT $1::bigint, unnest($2::bigint[]), NULL, $3::bigint
           ON CONFLICT DO NOTHING`,
          [req.params.id, req.body.team_role_ids, req.user.sub]
        );
      }

      return user.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Benutzer nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "USER_ACCESS_ASSIGNED", entityType: "user", entityId: Number(req.params.id), metadata: req.body, ipAddress: req.ip });
    const data = await withDbSession(req.user, getMatrix);
    res.json(data);
  } catch (err) {
    next(err);
  }
});

export default router;
