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 { writeAudit } from "../../utils/audit.js";

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

const categorySchema = z.object({
  code: z.string().min(1).max(80),
  label: z.string().min(2).max(120),
  description: z.string().max(2000).optional().nullable(),
  sort_order: z.coerce.number().int().min(0).max(999999).optional().default(1000),
  is_active: z.boolean().optional().default(true),
});

const updateCategorySchema = categorySchema.partial();

function normalizeCode(value) {
  return String(value || "")
    .trim()
    .toLowerCase()
    .replace(/ä/g, "ae")
    .replace(/ö/g, "oe")
    .replace(/ü/g, "ue")
    .replace(/ß/g, "ss")
    .replace(/[^a-z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "")
    .slice(0, 80);
}

function normalizeNullableText(value) {
  if (value === undefined) return undefined;
  if (value === null) return null;
  const trimmed = String(value).trim();
  return trimmed ? trimmed : null;
}

function mapCategory(row) {
  if (!row) return null;
  return {
    id: row.id,
    code: row.code,
    label: row.label,
    description: row.description,
    sort_order: row.sort_order,
    is_active: row.is_active,
    usage_count: row.usage_count ?? 0,
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
}

async function getCategoryById(db, id) {
  const result = await db.query(
    `SELECT c.id,
            c.code,
            c.label,
            c.description,
            c.sort_order,
            c.is_active,
            c.created_at,
            c.updated_at,
            (SELECT COUNT(*)::int FROM document_item d WHERE d.document_category_id = c.id AND d.deleted_at IS NULL) AS usage_count
       FROM document_category_catalog c
      WHERE c.id = $1`,
    [id]
  );
  return mapCategory(result.rows[0]);
}

router.get("/", async (req, res, next) => {
  try {
    const activeOnly = String(req.query.active || "").toLowerCase() === "true";
    const rows = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `SELECT c.id,
                c.code,
                c.label,
                c.description,
                c.sort_order,
                c.is_active,
                c.created_at,
                c.updated_at,
                (SELECT COUNT(*)::int FROM document_item d WHERE d.document_category_id = c.id AND d.deleted_at IS NULL) AS usage_count
           FROM document_category_catalog c
          WHERE ($1::boolean = FALSE OR c.is_active = TRUE)
          ORDER BY c.sort_order, c.label`,
        [activeOnly]
      );
      return result.rows.map(mapCategory);
    });

    res.json(rows);
  } catch (err) {
    next(err);
  }
});

router.post(
  "/",
  requireMfa,
  requirePermission("document_categories.manage", ["admin", "chair"]),
  validate(categorySchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        code: normalizeCode(req.body.code || req.body.label),
        label: String(req.body.label || "").trim(),
        description: normalizeNullableText(req.body.description),
        sort_order: Number(req.body.sort_order ?? 1000),
        is_active: req.body.is_active !== false,
      };

      if (!payload.code) return res.status(400).json({ error: "Code ist erforderlich" });

      const row = await withDbSession(req.user, async (db) => {
        const inserted = await db.query(
          `INSERT INTO document_category_catalog(code, label, description, sort_order, is_active, created_by, updated_by)
           VALUES ($1, $2, $3, $4, $5, $6, $6)
           RETURNING id`,
          [payload.code, payload.label, payload.description, payload.sort_order, payload.is_active, req.user.sub]
        );
        return getCategoryById(db, inserted.rows[0].id);
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "DOCUMENT_CATEGORY_CREATED",
        entityType: "document_category",
        entityId: row.id,
        ipAddress: req.ip,
        metadata: { payload },
      });

      res.status(201).json(row);
    } catch (err) {
      if (err?.code === "23505") {
        err.status = 409;
        err.publicMessage = "Eine Dokumentenkategorie mit diesem Code existiert bereits.";
      }
      next(err);
    }
  }
);

router.put(
  "/:id",
  requireMfa,
  requirePermission("document_categories.manage", ["admin", "chair"]),
  validate(updateCategorySchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        code: req.body.code === undefined ? undefined : normalizeCode(req.body.code || req.body.label),
        label: req.body.label === undefined ? undefined : String(req.body.label || "").trim(),
        description: normalizeNullableText(req.body.description),
        sort_order: req.body.sort_order === undefined ? undefined : Number(req.body.sort_order ?? 1000),
        is_active: req.body.is_active,
      };

      if (payload.code === "") return res.status(400).json({ error: "Code ist erforderlich" });
      if (payload.label === "") return res.status(400).json({ error: "Bezeichnung ist erforderlich" });

      const row = await withDbSession(req.user, async (db) => {
        const existing = await getCategoryById(db, req.params.id);
        if (!existing) return null;

        const fields = [];
        const values = [];
        let idx = 1;
        const push = (column, value) => {
          fields.push(`${column} = $${idx}`);
          values.push(value);
          idx += 1;
        };

        for (const [column, value] of [
          ["code", payload.code],
          ["label", payload.label],
          ["description", payload.description],
          ["sort_order", payload.sort_order],
          ["is_active", payload.is_active],
        ]) {
          if (value !== undefined) push(column, value);
        }

        if (!fields.length) return existing;
        push("updated_by", req.user.sub);
        values.push(req.params.id);

        await db.query(`UPDATE document_category_catalog SET ${fields.join(", ")} WHERE id = $${idx}`, values);
        return getCategoryById(db, req.params.id);
      });

      if (!row) return res.status(404).json({ error: "Nicht gefunden" });

      await writeAudit({
        actorId: req.user.sub,
        action: "DOCUMENT_CATEGORY_UPDATED",
        entityType: "document_category",
        entityId: row.id,
        ipAddress: req.ip,
        metadata: { payload },
      });

      res.json(row);
    } catch (err) {
      if (err?.code === "23505") {
        err.status = 409;
        err.publicMessage = "Eine Dokumentenkategorie mit diesem Code existiert bereits.";
      }
      next(err);
    }
  }
);

router.delete(
  "/:id",
  requireMfa,
  requirePermission("document_categories.manage", ["admin", "chair"]),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const existing = await getCategoryById(db, req.params.id);
        if (!existing) return null;

        if (Number(existing.usage_count || 0) > 0) {
          await db.query(
            `UPDATE document_category_catalog
                SET is_active = FALSE, updated_by = $2
              WHERE id = $1`,
            [req.params.id, req.user.sub]
          );
        } else {
          await db.query(`DELETE FROM document_category_catalog WHERE id = $1`, [req.params.id]);
        }

        return existing;
      });

      if (!row) return res.status(404).json({ error: "Nicht gefunden" });

      await writeAudit({
        actorId: req.user.sub,
        action: Number(row.usage_count || 0) > 0 ? "DOCUMENT_CATEGORY_DEACTIVATED" : "DOCUMENT_CATEGORY_DELETED",
        entityType: "document_category",
        entityId: row.id,
        ipAddress: req.ip,
      });

      res.json({ ok: true });
    } catch (err) {
      next(err);
    }
  }
);

export default router;
