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

function buildInsert(table, columns) {
  const names = columns.map((c) => c.column);
  const placeholders = names.map((_, i) => `$${i + 1}`);
  return `INSERT INTO ${table} (${names.join(", ")}) VALUES (${placeholders.join(", ")}) RETURNING *`;
}
function buildUpdate(table, columns, idColumn = "id") {
  const sets = columns.map((c, i) => `${c.column} = $${i + 1}`);
  return `UPDATE ${table} SET ${sets.join(", ")} WHERE ${idColumn} = $${columns.length + 1} RETURNING *`;
}

export function createCrudRouter(config) {
  const router = express.Router();
  const createSchema = z.object(Object.fromEntries(config.fields.map((f) => [f.name, f.schema])));
  const updateSchema = createSchema.partial();

  router.use(authenticate);

  router.get("/", async (req, res, next) => {
    try {
      const rows = await withDbSession(req.user, async (db) => {
        const result = await db.query(config.listSql);
        return result.rows;
      });
      res.json(rows);
    } catch (err) { next(err); }
  });

  router.get("/:id", async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(config.detailSql, [req.params.id]);
        return result.rows[0];
      });
      if (!row) return res.status(404).json({ error: "Nicht gefunden" });
      res.json(row);
    } catch (err) { next(err); }
  });

  router.post("/", requireMfa, ...(config.writePermission ? [requirePermission(config.writePermission, config.writeRoles || [])] : config.writeRoles ? [requireRoles(...config.writeRoles)] : []), validate(createSchema), async (req, res, next) => {
    try {
      const payload = req.body;
      const dbColumns = config.fields.map((f) => f.db || f.name);
      const values = config.fields.map((f) => payload[f.name] ?? null);
      const sql = buildInsert(config.table, config.fields.map((f) => ({ column: f.db || f.name })));
      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(sql, values);
        return result.rows[0];
      });
      await writeAudit({ actorId: req.user.sub, action: `${config.auditPrefix}_CREATED`, entityType: config.entityType, entityId: row.id, ipAddress: req.ip, metadata: { payload } });
      res.status(201).json(row);
    } catch (err) { next(err); }
  });

  router.put("/:id", requireMfa, ...(config.writePermission ? [requirePermission(config.writePermission, config.writeRoles || [])] : config.writeRoles ? [requireRoles(...config.writeRoles)] : []), validate(updateSchema), async (req, res, next) => {
    try {
      const presentFields = config.fields.filter((f) => req.body[f.name] !== undefined);
      if (!presentFields.length) return res.status(400).json({ error: "Keine Felder zum Aktualisieren" });
      const sql = buildUpdate(config.table, presentFields.map((f) => ({ column: f.db || f.name })), config.idColumn || "id");
      const values = presentFields.map((f) => req.body[f.name]);
      values.push(req.params.id);
      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(sql, values);
        return result.rows[0];
      });
      if (!row) return res.status(404).json({ error: "Nicht gefunden" });
      await writeAudit({ actorId: req.user.sub, action: `${config.auditPrefix}_UPDATED`, entityType: config.entityType, entityId: row.id, ipAddress: req.ip, metadata: { changes: req.body } });
      res.json(row);
    } catch (err) { next(err); }
  });

  router.delete("/:id", requireMfa, ...(config.writePermission ? [requirePermission(config.writePermission, config.writeRoles || [])] : config.writeRoles ? [requireRoles(...config.writeRoles)] : []), async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(config.deleteSql, [req.params.id]);
        return result.rows[0];
      });
      if (!row) return res.status(404).json({ error: "Nicht gefunden" });
      await writeAudit({ actorId: req.user.sub, action: `${config.auditPrefix}_DELETED`, entityType: config.entityType, entityId: row.id, ipAddress: req.ip });
      res.json({ ok: true, row });
    } catch (err) { next(err); }
  });

  return router;
}
