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";

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

const createSchema = z.object({
  user_id: z.coerce.number().int().positive(),
  notification_type: z.string().min(2).max(50),
  title: z.string().min(2).max(255),
  entity_type: z.string().max(50).optional().nullable(),
  entity_id: z.coerce.number().int().positive().optional().nullable(),
  is_read: z.boolean().default(false),
});

const updateSchema = createSchema.partial();

function parseBoolean(value, fallback = false) {
  if (value === undefined || value === null || value === "") return fallback;
  if (typeof value === "boolean") return value;
  const normalized = String(value).trim().toLowerCase();
  if (["1", "true", "yes", "ja", "on"].includes(normalized)) return true;
  if (["0", "false", "no", "nein", "off"].includes(normalized)) return false;
  return fallback;
}

function buildSearchPattern(raw) {
  const value = String(raw ?? "").trim();
  if (!value) return null;
  const escaped = value.replace(/\\/g, "\\\\");
  const hasWildcard = /[%_]/.test(escaped);
  return hasWildcard ? escaped : `%${escaped}%`;
}

function mapRow(row) {
  return {
    id: row.id,
    user_id: row.user_id,
    user_name: row.user_name,
    notification_type: row.notification_type,
    title: row.title,
    entity_type: row.entity_type,
    entity_id: row.entity_id,
    is_read: row.is_read,
    created_at: row.created_at,
  };
}

router.get("/", async (req, res, next) => {
  try {
    const onlyMine = parseBoolean(req.query.only_mine, false);
    const unreadFirst = parseBoolean(req.query.unread_first, true);
    const searchPattern = buildSearchPattern(req.query.q);

    const rows = await withDbSession(req.user, async (db) => {
      const values = [searchPattern, onlyMine, req.user.sub, unreadFirst];

      const result = await db.query(
        `
          SELECT n.id,
                 n.user_id,
                 u.full_name AS user_name,
                 n.notification_type,
                 n.title,
                 n.entity_type,
                 n.entity_id,
                 n.is_read,
                 n.created_at
            FROM notification_item n
            LEFT JOIN app_user u ON u.id = n.user_id
           WHERE (
                   $1::text IS NULL
                   OR n.title ILIKE $1 ESCAPE '\\'
                   OR n.notification_type ILIKE $1 ESCAPE '\\'
                   OR COALESCE(n.entity_type, '') ILIKE $1 ESCAPE '\\'
                   OR COALESCE(n.entity_id::text, '') ILIKE $1 ESCAPE '\\'
                   OR COALESCE(u.full_name, '') ILIKE $1 ESCAPE '\\'
                 )
             AND (
                   $2::boolean = FALSE
                   OR n.user_id = $3::bigint
                 )
           ORDER BY
             CASE WHEN $4::boolean THEN CASE WHEN n.is_read THEN 1 ELSE 0 END ELSE 0 END,
             n.created_at DESC,
             n.id DESC
        `,
        values
      );

      return result.rows.map(mapRow);
    });

    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(
        `
          SELECT n.id,
                 n.user_id,
                 u.full_name AS user_name,
                 n.notification_type,
                 n.title,
                 n.entity_type,
                 n.entity_id,
                 n.is_read,
                 n.created_at
            FROM notification_item n
            LEFT JOIN app_user u ON u.id = n.user_id
           WHERE n.id = $1
        `,
        [req.params.id]
      );

      return result.rows[0] ? mapRow(result.rows[0]) : null;
    });

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

router.post(
  "/:id/read",
  requireMfa,
  requirePermission("notifications.write", ["admin", "chair", "secretary", "member"]),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(
          `
            UPDATE notification_item
               SET is_read = TRUE
             WHERE id = $1
           RETURNING id, user_id, notification_type, title, entity_type, entity_id, is_read, created_at
          `,
          [req.params.id]
        );

        return result.rows[0] || null;
      });

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

      await writeAudit({
        actorId: req.user.sub,
        action: "NOTIFICATION_MARKED_READ",
        entityType: "notification",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
      });

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

router.post(
  "/mark-all-read",
  requireMfa,
  requirePermission("notifications.write", ["admin", "chair", "secretary", "member"]),
  async (req, res, next) => {
    try {
      const result = await withDbSession(req.user, async (db) =>
        db.query(
          `
            UPDATE notification_item
               SET is_read = TRUE
             WHERE is_read = FALSE
               AND user_id = $1
         RETURNING id
          `,
          [req.user.sub]
        )
      );

      await writeAudit({
        actorId: req.user.sub,
        action: "NOTIFICATION_MARKED_ALL_READ",
        entityType: "notification",
        entityId: null,
        ipAddress: req.ip,
        metadata: { count: result.rowCount || 0 },
      });

      res.json({ ok: true, count: result.rowCount || 0 });
    } catch (err) {
      next(err);
    }
  }
);

router.post(
  "/",
  requireMfa,
  requirePermission("notifications.write", ["admin", "chair", "secretary", "member"]),
  validate(createSchema),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(
          `
            INSERT INTO notification_item(
              user_id,
              notification_type,
              title,
              entity_type,
              entity_id,
              is_read
            )
            VALUES ($1, $2, $3, $4, $5, $6)
            RETURNING id, user_id, notification_type, title, entity_type, entity_id, is_read, created_at
          `,
          [
            req.body.user_id,
            req.body.notification_type,
            req.body.title,
            req.body.entity_type ?? null,
            req.body.entity_id ?? null,
            req.body.is_read ?? false,
          ]
        );
        return result.rows[0];
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "NOTIFICATION_CREATED",
        entityType: "notification",
        entityId: row.id,
        ipAddress: req.ip,
      });

      res.status(201).json(row);
    } catch (err) {
      next(err);
    }
  }
);

router.put(
  "/:id",
  requireMfa,
  requirePermission("notifications.write", ["admin", "chair", "secretary", "member"]),
  validate(updateSchema),
  async (req, res, next) => {
    try {
      const fields = [];
      const values = [];
      let idx = 1;

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

      if (req.body.user_id !== undefined) push("user_id", req.body.user_id);
      if (req.body.notification_type !== undefined) push("notification_type", req.body.notification_type);
      if (req.body.title !== undefined) push("title", req.body.title);
      if (req.body.entity_type !== undefined) push("entity_type", req.body.entity_type ?? null);
      if (req.body.entity_id !== undefined) push("entity_id", req.body.entity_id ?? null);
      if (req.body.is_read !== undefined) push("is_read", req.body.is_read);

      if (!fields.length) {
        return res.status(400).json({ error: "Keine Felder zum Aktualisieren" });
      }

      values.push(req.params.id);

      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(
          `
            UPDATE notification_item
               SET ${fields.join(", ")}
             WHERE id = $${idx}
         RETURNING id, user_id, notification_type, title, entity_type, entity_id, is_read, created_at
          `,
          values
        );

        return result.rows[0] || null;
      });

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

      await writeAudit({
        actorId: req.user.sub,
        action: "NOTIFICATION_UPDATED",
        entityType: "notification",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
      });

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

router.delete(
  "/:id",
  requireMfa,
  requirePermission("notifications.write", ["admin", "chair", "secretary", "member"]),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const result = await db.query(
          `DELETE FROM notification_item WHERE id = $1 RETURNING *`,
          [req.params.id]
        );
        return result.rows[0] || null;
      });

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

      await writeAudit({
        actorId: req.user.sub,
        action: "NOTIFICATION_DELETED",
        entityType: "notification",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
      });

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

export default router;
