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({
  title: z.string().min(3).max(255),
  description: z.string().max(8000).optional().nullable(),
  duration_minutes: z.coerce.number().int().min(0).max(600).optional().nullable(),
  case_id: z.coerce.number().int().positive().optional().nullable(),
});

const updateSchema = z.object({
  title: z.string().min(3).max(255).optional(),
  description: z.string().max(8000).optional().nullable(),
  duration_minutes: z.coerce.number().int().min(0).max(600).optional().nullable(),
  case_id: z.coerce.number().int().positive().optional().nullable(),
});

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

function normalizeNullableNumber(value) {
  if (value === undefined) return undefined;
  if (value === null || value === "") return null;
  return Number(value);
}

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 mapAgendaItem(row) {
  if (!row) return null;
  return {
    id: row.id,
    meeting_id: row.meeting_id,
    meeting_number: row.meeting_number,
    agenda_number: row.agenda_number,
    title: row.title,
    description: row.description,
    duration_minutes: row.duration_minutes,
    case_id: row.case_id,
    case_number: row.case_number,
    decision_id: row.decision_id,
    decision_number: row.decision_number,
    decision_title: row.decision_title,
    status: row.status || (row.meeting_id ? "in_sitzung" : "offen"),
    parent_agenda_item_id: row.parent_agenda_item_id,
    parent_agenda_number: row.parent_agenda_number,
    parent_title: row.parent_title,
    root_agenda_item_id: row.root_agenda_item_id,
    follow_up_type: row.follow_up_type,
    follow_up_note: row.follow_up_note,
    follow_up_created_from_meeting_id: row.follow_up_created_from_meeting_id,
    follow_up_source_meeting_number: row.follow_up_source_meeting_number,
    follow_up_created_from_agenda_item_id: row.follow_up_created_from_agenda_item_id,
    follow_up_created_at: row.follow_up_created_at,
    follow_up_created_by: row.follow_up_created_by,
    follow_up_created_by_name: row.follow_up_created_by_name,
    returned_from_meeting_id: row.returned_from_meeting_id,
    returned_at: row.returned_at,
    returned_by: row.returned_by,
    returned_by_name: row.returned_by_name,
    last_return_remark: row.last_return_remark,
    last_return_type: row.last_return_type,
    created_at: row.created_at,
  };
}

async function assertCaseReference(db, caseId) {
  if (caseId == null) return;
  const result = await db.query(`SELECT id FROM br_case WHERE id = $1 AND deleted_at IS NULL`, [caseId]);
  if (!result.rows[0]) {
    throw Object.assign(new Error("Vorgang nicht gefunden"), {
      status: 400,
      publicMessage: "Vorgang nicht gefunden",
    });
  }
}

async function getAgendaItems(db, options = {}) {
  const includeAssigned = Boolean(options.includeAssigned);
  const result = await db.query(
    `SELECT ai.id,
            ai.meeting_id,
            m.meeting_number,
            ai.agenda_number,
            ai.title,
            ai.description,
            ai.duration_minutes,
            ai.case_id,
            c.case_number,
            latest_decision.id AS decision_id,
            latest_decision.decision_number,
            latest_decision.title AS decision_title,
            ai.status,
            ai.parent_agenda_item_id,
            parent_ai.agenda_number AS parent_agenda_number,
            parent_ai.title AS parent_title,
            ai.root_agenda_item_id,
            ai.follow_up_type,
            ai.follow_up_note,
            ai.follow_up_created_from_meeting_id,
            source_meeting.meeting_number AS follow_up_source_meeting_number,
            ai.follow_up_created_from_agenda_item_id,
            ai.follow_up_created_at,
            ai.follow_up_created_by,
            fubu.full_name AS follow_up_created_by_name,
            ai.returned_from_meeting_id,
            ai.returned_at,
            ai.returned_by,
            ru.full_name AS returned_by_name,
            COALESCE(ai.follow_up_note, last_return.remark) AS last_return_remark,
            COALESCE(ai.follow_up_type, last_return.return_type) AS last_return_type,
            ai.created_at
       FROM meeting_agenda_item ai
       LEFT JOIN meeting m ON m.id = ai.meeting_id
       LEFT JOIN br_case c ON c.id = ai.case_id
       LEFT JOIN meeting_agenda_item parent_ai ON parent_ai.id = ai.parent_agenda_item_id
       LEFT JOIN meeting source_meeting ON source_meeting.id = ai.follow_up_created_from_meeting_id
       LEFT JOIN app_user fubu ON fubu.id = ai.follow_up_created_by
       LEFT JOIN app_user ru ON ru.id = ai.returned_by
       LEFT JOIN LATERAL (
         SELECT d.id, d.decision_number, d.title
           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
       LEFT JOIN LATERAL (
         SELECT l.remark, l.return_type
           FROM meeting_agenda_item_return_log l
          WHERE l.agenda_item_id = ai.id
          ORDER BY l.returned_at DESC, l.id DESC
          LIMIT 1
       ) last_return ON TRUE
      WHERE ($1::boolean = TRUE OR ai.meeting_id IS NULL)
      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`,
    [includeAssigned]
  );

  return result.rows.map(mapAgendaItem);
}

async function getAgendaItemById(db, id) {
  const result = await db.query(
    `SELECT ai.id,
            ai.meeting_id,
            m.meeting_number,
            ai.agenda_number,
            ai.title,
            ai.description,
            ai.duration_minutes,
            ai.case_id,
            c.case_number,
            latest_decision.id AS decision_id,
            latest_decision.decision_number,
            latest_decision.title AS decision_title,
            ai.status,
            ai.parent_agenda_item_id,
            parent_ai.agenda_number AS parent_agenda_number,
            parent_ai.title AS parent_title,
            ai.root_agenda_item_id,
            ai.follow_up_type,
            ai.follow_up_note,
            ai.follow_up_created_from_meeting_id,
            source_meeting.meeting_number AS follow_up_source_meeting_number,
            ai.follow_up_created_from_agenda_item_id,
            ai.follow_up_created_at,
            ai.follow_up_created_by,
            fubu.full_name AS follow_up_created_by_name,
            ai.returned_from_meeting_id,
            ai.returned_at,
            ai.returned_by,
            ru.full_name AS returned_by_name,
            COALESCE(ai.follow_up_note, last_return.remark) AS last_return_remark,
            COALESCE(ai.follow_up_type, last_return.return_type) AS last_return_type,
            ai.created_at
       FROM meeting_agenda_item ai
       LEFT JOIN meeting m ON m.id = ai.meeting_id
       LEFT JOIN br_case c ON c.id = ai.case_id
       LEFT JOIN meeting_agenda_item parent_ai ON parent_ai.id = ai.parent_agenda_item_id
       LEFT JOIN meeting source_meeting ON source_meeting.id = ai.follow_up_created_from_meeting_id
       LEFT JOIN app_user fubu ON fubu.id = ai.follow_up_created_by
       LEFT JOIN app_user ru ON ru.id = ai.returned_by
       LEFT JOIN LATERAL (
         SELECT d.id, d.decision_number, d.title
           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
       LEFT JOIN LATERAL (
         SELECT l.remark, l.return_type
           FROM meeting_agenda_item_return_log l
          WHERE l.agenda_item_id = ai.id
          ORDER BY l.returned_at DESC, l.id DESC
          LIMIT 1
       ) last_return ON TRUE
      WHERE ai.id = $1`,
    [id]
  );

  return mapAgendaItem(result.rows[0]);
}

router.get("/", async (req, res, next) => {
  try {
    const includeAssigned = parseBoolean(req.query.include_assigned, false) || parseBoolean(req.query.all, false);
    const rows = await withDbSession(req.user, (db) => getAgendaItems(db, { includeAssigned }));
    res.json(rows);
  } catch (err) {
    next(err);
  }
});

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

router.post(
  "/",
  requireMfa,
  requirePermission("agenda.write", ["admin", "chair", "secretary"]),
  validate(createSchema),
  async (req, res, next) => {
    try {
      const payload = {
        title: req.body.title.trim(),
        description: normalizeNullableText(req.body.description),
        duration_minutes: req.body.duration_minutes ?? null,
        case_id: normalizeNullableNumber(req.body.case_id),
      };

      const row = await withDbSession(req.user, async (db) => {
        await assertCaseReference(db, payload.case_id);

        const result = await db.query(
          `INSERT INTO meeting_agenda_item(meeting_id, agenda_number, title, description, duration_minutes, case_id, status)
           VALUES (NULL, NULL, $1, $2, $3, $4, 'offen')
           RETURNING id`,
          [payload.title, payload.description, payload.duration_minutes, payload.case_id]
        );

        return getAgendaItemById(db, result.rows[0].id);
      });

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

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

router.put(
  "/:id",
  requireMfa,
  requirePermission("agenda.write", ["admin", "chair", "secretary"]),
  validate(updateSchema),
  async (req, res, next) => {
    try {
      const payload = {
        title: req.body.title !== undefined ? req.body.title.trim() : undefined,
        description: normalizeNullableText(req.body.description),
        duration_minutes: req.body.duration_minutes,
        case_id: normalizeNullableNumber(req.body.case_id),
      };

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

        await assertCaseReference(db, payload.case_id);

        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 [
          ["title", payload.title],
          ["description", payload.description],
          ["duration_minutes", payload.duration_minutes],
          ["case_id", payload.case_id],
        ]) {
          if (value !== undefined) push(column, value);
        }

        if (existing.meeting_id === null && ["rueckfrage", "klaerung"].includes(existing.status)) {
          push("status", "offen");
        }

        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 meeting_agenda_item SET ${fields.join(", ")} WHERE id = $${idx}`, values);
        return getAgendaItemById(db, req.params.id);
      });

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

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

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

router.delete("/:id", requireMfa, requirePermission("agenda.write", ["admin", "chair", "secretary"]), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const existing = await getAgendaItemById(db, req.params.id);
      if (!existing) return null;
      await db.query(`DELETE FROM meeting_agenda_item 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: "AGENDA_ITEM_DELETED_POOL",
      entityType: "agenda_item",
      entityId: row.id,
      ipAddress: req.ip,
    });

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

export default router;
