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 writeGuards = [requireMfa, requirePermission("comments.write", ["admin", "chair", "secretary", "member"])];

const createSchema = z.object({
  entity_type: z.string().min(2).max(50).default("agenda_item"),
  entity_id: z.coerce.number().int().positive(),
  comment_text: z.string().min(1).max(10000),
  visibility: z.enum(["intern", "vertraulich"]).default("intern"),
  include_in_protocol: z.coerce.boolean().optional().default(false),
});

const updateSchema = createSchema.partial();

function mapComment(row) {
  if (!row) return null;
  return {
    id: row.id,
    entity_type: row.entity_type,
    entity_id: row.entity_id,
    comment_text: row.comment_text,
    visibility: row.visibility,
    include_in_protocol: Boolean(row.include_in_protocol),
    author_user_id: row.author_user_id,
    author_name: row.author_name,
    created_at: row.created_at,
    agenda_item_id: row.agenda_item_id,
    agenda_item_title: row.agenda_item_title,
    agenda_number: row.agenda_number,
    meeting_id: row.meeting_id,
    meeting_number: row.meeting_number,
    meeting_date: row.meeting_date,
    meeting_status: row.meeting_status,
    case_number: row.case_number,
    decision_number: row.decision_number,
    decision_status: row.decision_status,
  };
}

function mapAgendaItem(row) {
  if (!row) return null;
  return {
    id: row.id,
    title: row.title,
    agenda_number: row.agenda_number,
    meeting_id: row.meeting_id,
    meeting_number: row.meeting_number,
    meeting_date: row.meeting_date,
    meeting_status: row.meeting_status,
    case_number: row.case_number,
    decision_number: row.decision_number,
    decision_status: row.decision_status,
  };
}

const commentListSql = `
  SELECT c.id,
         c.entity_type,
         c.entity_id,
         c.comment_text,
         c.visibility,
         COALESCE(c.include_in_protocol, FALSE) AS include_in_protocol,
         c.author_user_id,
         au.full_name AS author_name,
         c.created_at,
         ai.id AS agenda_item_id,
         ai.title AS agenda_item_title,
         ai.agenda_number,
         ai.meeting_id,
         m.meeting_number,
         m.meeting_date,
         m.status AS meeting_status,
         bc.case_number,
         latest_decision.decision_number,
         latest_decision.status AS decision_status
    FROM comment_item c
    LEFT JOIN app_user au ON au.id = c.author_user_id
    LEFT JOIN meeting_agenda_item ai ON c.entity_type = 'agenda_item' AND ai.id = c.entity_id
    LEFT JOIN meeting m ON m.id = ai.meeting_id
    LEFT JOIN br_case bc ON bc.id = ai.case_id
    LEFT JOIN LATERAL (
      SELECT d.decision_number, d.status
        FROM decision d
       WHERE d.agenda_item_id = ai.id
       ORDER BY d.created_at DESC, d.id DESC
       LIMIT 1
    ) latest_decision ON TRUE
   WHERE c.deleted_at IS NULL
   ORDER BY c.created_at DESC, c.id DESC`;

const openAgendaItemsSql = `
  SELECT ai.id,
         ai.title,
         ai.agenda_number,
         ai.meeting_id,
         m.meeting_number,
         m.meeting_date,
         m.status AS meeting_status,
         bc.case_number,
         latest_decision.decision_number,
         latest_decision.status AS decision_status
    FROM meeting_agenda_item ai
    LEFT JOIN meeting m ON m.id = ai.meeting_id
    LEFT JOIN br_case bc ON bc.id = ai.case_id
    LEFT JOIN LATERAL (
      SELECT d.decision_number, d.status
        FROM decision d
       WHERE d.agenda_item_id = ai.id
       ORDER BY d.created_at DESC, d.id DESC
       LIMIT 1
    ) latest_decision ON TRUE
   WHERE ai.meeting_id IS NULL
     AND (latest_decision.status IS NULL OR latest_decision.status <> 'abgeschlossen')
   ORDER BY m.meeting_date DESC NULLS LAST,
            COALESCE(NULLIF(regexp_replace(COALESCE(ai.agenda_number, ''), '[^0-9]', '', 'g'), ''), '999999')::int,
            ai.created_at DESC,
            ai.id DESC`;

async function getCommentById(db, id) {
  const result = await db.query(`${commentListSql.replace("ORDER BY c.created_at DESC, c.id DESC", "AND c.id = $1")}`, [id]);
  return mapComment(result.rows[0]);
}

async function assertCommentTarget(db, entityType, entityId, options = {}) {
  if (entityType !== "agenda_item") return;

  const result = await db.query(
    `SELECT ai.id,
            ai.meeting_id,
            latest_decision.status AS decision_status
       FROM meeting_agenda_item ai
       LEFT JOIN LATERAL (
         SELECT d.status
           FROM decision d
          WHERE d.agenda_item_id = ai.id
          ORDER BY d.created_at DESC, d.id DESC
          LIMIT 1
       ) latest_decision ON TRUE
      WHERE ai.id = $1`,
    [entityId]
  );

  const row = result.rows[0];
  if (!row) {
    throw Object.assign(new Error("TOP nicht gefunden"), {
      status: 400,
      publicMessage: "Der ausgewählte TOP wurde nicht gefunden.",
    });
  }

  if (options.allowExistingTarget) return;

  if (row.meeting_id !== null) {
    throw Object.assign(new Error("TOP ist bereits einer Sitzung zugeordnet"), {
      status: 400,
      publicMessage: "Kommentare können nur mit offenen TOPs aus dem TOP-Bestand verknüpft werden.",
    });
  }

  if (row.decision_status === "abgeschlossen") {
    throw Object.assign(new Error("TOP ist erledigt"), {
      status: 400,
      publicMessage: "Kommentare können nur mit TOPs verknüpft werden, die noch nicht erledigt sind.",
    });
  }
}

router.get("/open-agenda-items", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, async (db) => {
      const result = await db.query(openAgendaItemsSql);
      return result.rows.map(mapAgendaItem);
    });
    res.json(rows);
  } catch (err) {
    next(err);
  }
});

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

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

router.post("/", ...writeGuards, validate(createSchema), async (req, res, next) => {
  try {
    const payload = {
      entity_type: req.body.entity_type || "agenda_item",
      entity_id: Number(req.body.entity_id),
      comment_text: req.body.comment_text.trim(),
      visibility: req.body.visibility || "intern",
      author_user_id: Number(req.user.sub),
      include_in_protocol: Boolean(req.body.include_in_protocol),
    };

    const row = await withDbSession(req.user, async (db) => {
      await assertCommentTarget(db, payload.entity_type, payload.entity_id);
      const result = await db.query(
        `INSERT INTO comment_item(entity_type, entity_id, comment_text, visibility, author_user_id, include_in_protocol)
         VALUES ($1, $2, $3, $4, $5, $6)
         RETURNING id`,
        [payload.entity_type, payload.entity_id, payload.comment_text, payload.visibility, payload.author_user_id, payload.include_in_protocol]
      );
      return getCommentById(db, result.rows[0].id);
    });

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

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

router.put("/:id", ...writeGuards, validate(updateSchema), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const existing = await getCommentById(db, req.params.id);
      if (!existing) return null;

      const nextEntityType = req.body.entity_type ?? existing.entity_type;
      const nextEntityId = req.body.entity_id !== undefined ? Number(req.body.entity_id) : existing.entity_id;
      const targetUnchanged = nextEntityType === existing.entity_type && Number(nextEntityId) === Number(existing.entity_id);
      await assertCommentTarget(db, nextEntityType, nextEntityId, { allowExistingTarget: targetUnchanged });

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

      if (req.body.entity_type !== undefined) push("entity_type", req.body.entity_type);
      if (req.body.entity_id !== undefined) push("entity_id", Number(req.body.entity_id));
      if (req.body.comment_text !== undefined) push("comment_text", req.body.comment_text.trim());
      if (req.body.visibility !== undefined) push("visibility", req.body.visibility);
      if (req.body.include_in_protocol !== undefined) push("include_in_protocol", Boolean(req.body.include_in_protocol));

      if (!fields.length) {
        throw Object.assign(new Error("Keine Felder zum Aktualisieren"), {
          status: 400,
          publicMessage: "Keine Felder zum Aktualisieren.",
        });
      }

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

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

    await writeAudit({
      actorId: req.user.sub,
      action: "COMMENT_UPDATED",
      entityType: "comment",
      entityId: row.id,
      ipAddress: req.ip,
      metadata: { changes: Object.keys(req.body || {}) },
    });

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

router.delete("/:id", ...writeGuards, async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const existing = await getCommentById(db, req.params.id);
      if (!existing) return null;
      await db.query(`UPDATE comment_item SET deleted_at = NOW() 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: "COMMENT_DELETED",
      entityType: "comment",
      entityId: row.id,
      ipAddress: req.ip,
    });

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

export default router;
