import express from "express";
import crypto from "crypto";
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";
import { upload } from "../../middleware/upload.js";
import { scanBuffer } from "../../services/clamav.js";
import { saveObject } from "../../services/storage.js";
import { fileTypeFromBuffer } from "file-type";
import { v4 as uuidv4 } from "uuid";

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

const priorityEnum = z.enum(["sehr_hoch", "hoch", "mittel", "niedrig"]);
const confEnum = z.enum(["intern", "vertraulich", "streng_vertraulich"]);

const createSchema = z.object({
  case_number: z.string().min(3).max(50).optional().nullable(),
  title: z.string().min(3).max(255),
  case_type_id: z.coerce.number().int().positive(),
  status: z.string().min(2).max(50).default("neu"),
  priority: priorityEnum.default("mittel"),
  confidentiality_level: confEnum.default("intern"),
  summary: z.string().max(8000).optional().nullable(),
  affected_employee_name: z.string().max(120).optional().nullable(),
  department: z.string().max(120).optional().nullable(),
  committee_id: z.coerce.number().int().positive().nullable().optional(),
  owner_user_id: z.coerce.number().int().positive().nullable().optional(),
  legal_hold: z.boolean().optional().default(false),
  team_role_ids: z.array(z.coerce.number().int().positive()).optional().default([]),
});

const updateSchema = z.object({
  title: z.string().min(3).max(255).optional(),
  case_type_id: z.coerce.number().int().positive().optional(),
  status: z.string().min(2).max(50).optional(),
  priority: priorityEnum.optional(),
  confidentiality_level: confEnum.optional(),
  summary: z.string().max(8000).optional().nullable(),
  affected_employee_name: z.string().max(120).optional().nullable(),
  department: z.string().max(120).optional().nullable(),
  committee_id: z.coerce.number().int().positive().nullable().optional(),
  owner_user_id: z.coerce.number().int().positive().nullable().optional(),
  legal_hold: z.boolean().optional(),
  team_role_ids: z.array(z.coerce.number().int().positive()).optional(),
});

const addToAgendaSchema = z.object({
  meeting_id: z.coerce.number().int().positive(),
  agenda_item_id: z.coerce.number().int().positive().optional().nullable(),
  duration_minutes: z.coerce.number().int().min(0).max(600).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 normalizeOptionalText(value) {
  if (value === undefined) return undefined;
  if (value === null) return null;
  const trimmed = String(value).trim();
  return trimmed || null;
}

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

function mapCaseRow(row) {
  if (!row) return null;
  return {
    id: row.id,
    case_number: row.case_number,
    title: row.title,
    case_type_id: row.case_type_id,
    case_type: row.case_type,
    status: row.status,
    priority: row.priority,
    confidentiality_level: row.confidentiality_level,
    summary: row.summary,
    affected_employee_name: row.affected_employee_name,
    department: row.department,
    department_label: row.department_label,
    committee_id: row.committee_id,
    committee_name: row.committee_name,
    owner_user_id: row.owner_user_id,
    owner_name: row.owner_name,
    legal_hold: row.legal_hold,
    attachment_count: row.attachment_count,
    team_role_ids: Array.isArray(row.team_role_ids) ? row.team_role_ids.map(Number) : [],
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
}

function isMeetingOpen(row) {
  if (!row) return false;
  if (row.deleted_at) return false;
  if (row.is_closed === true) return false;
  if (row.closed === true) return false;
  if (row.closed_at) return false;

  const status = String(row.status ?? "").trim().toLowerCase();
  if (["geschlossen", "closed", "archiviert", "archived", "beendet"].includes(status)) {
    return false;
  }

  return true;
}

function mapMeetingMetaRow(row) {
  return {
    id: row.id,
    meeting_number: row.meeting_number,
    title: row.title,
    meeting_date: row.meeting_date,
    committee_id: row.committee_id,
    status: row.status ?? null,
    is_closed: row.is_closed ?? null,
    closed: row.closed ?? null,
    closed_at: row.closed_at ?? null,
  };
}

function buildSafeStorageKey(originalname) {
  const safeName = String(originalname || "datei")
    .replace(/[^a-zA-Z0-9._-]/g, "_")
    .replace(/_+/g, "_");
  return `${new Date().toISOString().slice(0, 10)}/${uuidv4()}-${safeName}`;
}

function generateDocumentNumber() {
  return `D-${new Date().getFullYear()}-${Math.floor(Math.random() * 100000)
    .toString()
    .padStart(5, "0")}`;
}

function collectUploadedFiles(req) {
  const files = [];

  if (Array.isArray(req.files)) {
    files.push(...req.files);
  } else if (req.files && typeof req.files === "object") {
    if (Array.isArray(req.files.files)) files.push(...req.files.files);
    if (Array.isArray(req.files.file)) files.push(...req.files.file);
  }

  if (req.file) files.push(req.file);

  return files;
}

async function analyzeUpload(file) {
  const mime = await fileTypeFromBuffer(file.buffer);
  const detectedMime = mime?.mime || file.mimetype || "application/octet-stream";
  const sha256 = crypto.createHash("sha256").update(file.buffer).digest("hex");
  const scan = await scanBuffer(file.buffer);

  return {
    file,
    detectedMime,
    sha256,
    scan,
  };
}

async function getCaseList(db) {
  const result = await db.query(
    `SELECT c.id,
            c.case_number,
            c.title,
            c.case_type_id,
            COALESCE(ct.label, c.case_type) AS case_type,
            c.status,
            c.priority,
            c.confidentiality_level,
            c.summary,
            c.affected_employee_name,
            c.department,
            COALESCE(dc.label, c.department) AS department_label,
            c.committee_id,
            cm.name AS committee_name,
            c.owner_user_id,
            u.full_name AS owner_name,
            c.legal_hold,
            c.created_at,
            c.updated_at,
            (
              SELECT COUNT(*)::int
              FROM document_item d
              WHERE d.case_id = c.id
                AND d.deleted_at IS NULL
            ) AS attachment_count,
            COALESCE((
              SELECT ARRAY_AGG(cta.team_role_id ORDER BY cta.team_role_id)
                FROM br_case_team_access cta
               WHERE cta.case_id = c.id
                 AND cta.can_read = TRUE
            ), ARRAY[]::BIGINT[]) AS team_role_ids
       FROM br_case c
       LEFT JOIN case_type_catalog ct ON ct.id = c.case_type_id
       LEFT JOIN department_catalog dc ON dc.code = c.department
       LEFT JOIN committee cm ON cm.id = c.committee_id
       LEFT JOIN app_user u ON u.id = c.owner_user_id
      WHERE c.deleted_at IS NULL
      ORDER BY c.created_at DESC`
  );

  return result.rows.map(mapCaseRow);
}

async function getCaseById(db, id) {
  const result = await db.query(
    `SELECT c.id,
            c.case_number,
            c.title,
            c.case_type_id,
            COALESCE(ct.label, c.case_type) AS case_type,
            c.status,
            c.priority,
            c.confidentiality_level,
            c.summary,
            c.affected_employee_name,
            c.department,
            COALESCE(dc.label, c.department) AS department_label,
            c.committee_id,
            cm.name AS committee_name,
            c.owner_user_id,
            u.full_name AS owner_name,
            c.legal_hold,
            c.created_at,
            c.updated_at,
            (
              SELECT COUNT(*)::int
              FROM document_item d
              WHERE d.case_id = c.id
                AND d.deleted_at IS NULL
            ) AS attachment_count,
            COALESCE((
              SELECT ARRAY_AGG(cta.team_role_id ORDER BY cta.team_role_id)
                FROM br_case_team_access cta
               WHERE cta.case_id = c.id
                 AND cta.can_read = TRUE
            ), ARRAY[]::BIGINT[]) AS team_role_ids
       FROM br_case c
       LEFT JOIN case_type_catalog ct ON ct.id = c.case_type_id
       LEFT JOIN department_catalog dc ON dc.code = c.department
       LEFT JOIN committee cm ON cm.id = c.committee_id
       LEFT JOIN app_user u ON u.id = c.owner_user_id
      WHERE c.id = $1
        AND c.deleted_at IS NULL`,
    [id]
  );

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

async function getUnassignedAgendaItemForCase(db, caseId) {
  const result = await db.query(
    `SELECT id, meeting_id, case_id, title, description, duration_minutes
       FROM meeting_agenda_item
      WHERE case_id = $1
        AND meeting_id IS NULL
      ORDER BY created_at ASC, id ASC
      LIMIT 1`,
    [caseId]
  );

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

async function ensureAgendaItemForCase(db, currentCase, durationMinutes = null) {
  const existing = await getUnassignedAgendaItemForCase(db, currentCase.id);
  if (existing) return existing;

  const created = await db.query(
    `INSERT INTO meeting_agenda_item(meeting_id, agenda_number, title, description, duration_minutes, case_id)
     VALUES (NULL, NULL, $1, $2, $3, $4)
     RETURNING id, meeting_id, case_id, title, description, duration_minutes`,
    [
      `${currentCase.case_number} – ${currentCase.title}`,
      currentCase.summary || `Automatisch aus Vorgang ${currentCase.case_number} erzeugt`,
      durationMinutes ?? null,
      currentCase.id,
    ]
  );

  return created.rows[0];
}

async function getCaseTypeById(db, caseTypeId) {
  if (caseTypeId == null) return null;
  const ref = await db.query(
    `SELECT id, code, label
       FROM case_type_catalog
      WHERE id = $1
        AND is_active = TRUE`,
    [caseTypeId]
  );
  return ref.rows[0] || null;
}

async function syncCaseTeamAccess(db, caseId, teamRoleIds, actorId) {
  if (!Array.isArray(teamRoleIds)) return;
  await db.query(`DELETE FROM br_case_team_access WHERE case_id = $1`, [caseId]);
  if (!teamRoleIds.length) return;
  await db.query(
    `INSERT INTO br_case_team_access(case_id, team_role_id, can_read, can_write, created_by)
     SELECT $1::bigint, unnest($2::bigint[]), TRUE, FALSE, $3::bigint
     ON CONFLICT (case_id, team_role_id) DO UPDATE SET can_read = TRUE`,
    [caseId, teamRoleIds.map(Number), actorId]
  );
}

async function assertReferences(db, payload) {
  let caseType = null;

  if (payload.case_type_id !== undefined) {
    caseType = await getCaseTypeById(db, payload.case_type_id);
    if (!caseType) {
      throw Object.assign(new Error("Vorgangstyp nicht gefunden"), {
        status: 400,
        publicMessage: "Vorgangstyp nicht gefunden",
      });
    }
  }

  if (payload.owner_user_id !== undefined && payload.owner_user_id !== null) {
    const ref = await db.query(
      `SELECT id
         FROM app_user
        WHERE id = $1
          AND is_active = TRUE`,
      [payload.owner_user_id]
    );
    if (!ref.rows[0]) {
      throw Object.assign(new Error("Verantwortliche Person nicht gefunden"), {
        status: 400,
        publicMessage: "Verantwortliche Person nicht gefunden",
      });
    }
  }

  if (payload.committee_id !== undefined && payload.committee_id !== null) {
    const ref = await db.query(`SELECT id FROM committee WHERE id = $1`, [payload.committee_id]);
    if (!ref.rows[0]) {
      throw Object.assign(new Error("Ausschuss nicht gefunden"), {
        status: 400,
        publicMessage: "Ausschuss nicht gefunden",
      });
    }
  }

  if (payload.department !== undefined && payload.department !== null) {
    const ref = await db.query(
      `SELECT id
         FROM department_catalog
        WHERE code = $1
          AND is_active = TRUE`,
      [payload.department]
    );
    if (!ref.rows[0]) {
      throw Object.assign(new Error("Abteilung nicht gefunden"), {
        status: 400,
        publicMessage: "Abteilung nicht gefunden oder nicht aktiv",
      });
    }
  }

  return { caseType };
}

router.get("/meta", async (req, res, next) => {
  try {
    const data = await withDbSession(req.user, async (db) => {
      const [caseTypes, users, committees, meetings, teamRoles, departments] = await Promise.all([
        db.query(`SELECT id, code, label FROM case_type_catalog WHERE is_active = TRUE ORDER BY sort_order, label`),
        db.query(`SELECT id, full_name, email, committee_id FROM app_user WHERE is_active = TRUE ORDER BY full_name`),
        db.query(`SELECT id, name, committee_type FROM committee ORDER BY name`),
        db.query(`SELECT * FROM meeting ORDER BY meeting_date DESC, id DESC LIMIT 500`),
        db.query(`SELECT id, code, name, can_view_cases, can_write_cases, can_view_documents, can_write_documents FROM app_team_role WHERE is_active = TRUE ORDER BY name`),
        db.query(`SELECT id, code, label FROM department_catalog WHERE is_active = TRUE ORDER BY sort_order, label`),
      ]);

      return {
        caseTypes: caseTypes.rows,
        users: users.rows,
        committees: committees.rows,
        meetings: meetings.rows.filter(isMeetingOpen).map(mapMeetingMetaRow),
        teamRoles: teamRoles.rows,
        departments: departments.rows,
      };
    });

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

router.get("/", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, getCaseList);
    res.json(rows);
  } catch (err) {
    next(err);
  }
});

router.get("/:id", async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, (db) => getCaseById(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("cases.write", ["admin", "chair", "secretary", "member"]),
  validate(createSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        case_number: normalizeOptionalText(req.body.case_number),
        summary: normalizeNullableText(req.body.summary),
        affected_employee_name: normalizeNullableText(req.body.affected_employee_name),
        department: normalizeNullableText(req.body.department),
        committee_id: normalizeNullableNumber(req.body.committee_id),
        owner_user_id: normalizeNullableNumber(req.body.owner_user_id) ?? req.user.sub,
      };

      const row = await withDbSession(req.user, async (db) => {
        const { caseType } = await assertReferences(db, payload);

        const insert = await db.query(
          `INSERT INTO br_case(
             case_number,
             title,
             case_type,
             case_type_id,
             status,
             priority,
             confidentiality_level,
             summary,
             affected_employee_name,
             department,
             committee_id,
             owner_user_id,
             legal_hold,
             created_by,
             updated_by
           ) VALUES (
             COALESCE(NULLIF($1, ''), next_case_number()),
             $2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$14
           )
           RETURNING id`,
          [
            payload.case_number,
            payload.title,
            caseType.label,
            payload.case_type_id,
            payload.status,
            payload.priority,
            payload.confidentiality_level,
            payload.summary,
            payload.affected_employee_name,
            payload.department,
            payload.committee_id,
            payload.owner_user_id,
            payload.legal_hold ?? false,
            req.user.sub,
          ]
        );

        await syncCaseTeamAccess(db, insert.rows[0].id, payload.team_role_ids || [], req.user.sub);

        const row = await getCaseById(db, insert.rows[0].id);

        await ensureAgendaItemForCase(db, {
          id: row.id,
          case_number: row.case_number,
          title: row.title,
          summary: row.summary,
        });

        return row;
      });

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

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

router.put(
  "/:id",
  requireMfa,
  requirePermission("cases.write", ["admin", "chair", "secretary", "member"]),
  validate(updateSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        summary: normalizeNullableText(req.body.summary),
        affected_employee_name: normalizeNullableText(req.body.affected_employee_name),
        department: normalizeNullableText(req.body.department),
        committee_id: normalizeNullableNumber(req.body.committee_id),
        owner_user_id: normalizeNullableNumber(req.body.owner_user_id),
      };

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

        const { caseType } = await assertReferences(db, payload);

        const fields = [];
        const values = [];
        let idx = 1;

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

        const allowed = [
          ["title", payload.title],
          ["case_type_id", payload.case_type_id],
          ["status", payload.status],
          ["priority", payload.priority],
          ["confidentiality_level", payload.confidentiality_level],
          ["summary", payload.summary],
          ["affected_employee_name", payload.affected_employee_name],
          ["department", payload.department],
          ["committee_id", payload.committee_id],
          ["owner_user_id", payload.owner_user_id],
          ["legal_hold", payload.legal_hold],
        ];

        for (const [column, value] of allowed) {
          if (value !== undefined) push(column, value);
        }

        if (caseType) {
          push("case_type", caseType.label);
        }

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

        if (fields.length) {
          push("updated_by", req.user.sub);

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

        if (payload.team_role_ids !== undefined) {
          await syncCaseTeamAccess(db, req.params.id, payload.team_role_ids || [], req.user.sub);
        }

        return getCaseById(db, req.params.id);
      });

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

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

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

router.delete("/:id", requireMfa, requirePermission("cases.delete", ["admin", "chair"]), async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const existing = await getCaseById(db, req.params.id);
      if (!existing) return null;
      await db.query(`UPDATE br_case SET deleted_at = NOW(), updated_by = $2 WHERE id = $1`, [req.params.id, req.user.sub]);
      return existing;
    });

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

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

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

router.post(
  "/:id/attachments",
  requireMfa,
  requirePermission("cases.write", ["admin", "chair", "secretary", "member"]),
  upload.fields([
    { name: "files", maxCount: 20 },
    { name: "file", maxCount: 1 },
  ]),
  async (req, res, next) => {
    try {
      const files = collectUploadedFiles(req);
      if (!files.length) return res.status(400).json({ error: "Datei fehlt" });

      const analyzedFiles = await Promise.all(files.map(analyzeUpload));
      const infectedFiles = analyzedFiles
        .filter((item) => !item.scan.clean)
        .map((item) => ({
          file_name: item.file.originalname,
          scan_result: item.scan.raw,
        }));

      if (infectedFiles.length > 0) {
        return res.status(422).json({
          error: "Mindestens eine Datei wurde durch den Virenscan abgelehnt",
          files: infectedFiles,
        });
      }

      const result = await withDbSession(req.user, async (db) => {
        const caseRow = await getCaseById(db, req.params.id);
        if (!caseRow) {
          throw Object.assign(new Error("Vorgang nicht gefunden"), {
            status: 404,
            publicMessage: "Vorgang nicht gefunden",
          });
        }

        const insertedRows = [];

        for (const item of analyzedFiles) {
          const storageKey = buildSafeStorageKey(item.file.originalname);
          const docMeta = await saveObject(storageKey, item.file.buffer, item.detectedMime);

          const insert = await db.query(
            `INSERT INTO document_item(
               document_number,
               title,
               document_type,
               description,
               version_label,
               file_name,
               storage_path,
               storage_provider,
               mime_type,
               size_bytes,
               sha256_hash,
               confidentiality_level,
               case_id,
               meeting_id,
               decision_id,
               uploaded_by,
               scan_status,
               scan_engine,
               scan_result,
               uploaded_at
             ) VALUES (
               $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,NULL,NULL,$14,$15,$16,$17,NOW()
             ) RETURNING id, document_number, title, document_type, file_name, case_id, uploaded_at, scan_status`,
            [
              req.body.document_number || generateDocumentNumber(),
              req.body.title || item.file.originalname,
              req.body.document_type || "anlage",
              req.body.description || `Anlage zu Vorgang ${caseRow.case_number}`,
              req.body.version_label || "v1",
              item.file.originalname,
              docMeta.storage_path,
              docMeta.storage_provider,
              item.detectedMime,
              item.file.size,
              item.sha256,
              req.body.confidentiality_level || caseRow.confidentiality_level || "intern",
              req.params.id,
              req.user.sub,
              "clean",
              "clamav",
              item.scan.raw,
            ]
          );

          await db.query(
            `INSERT INTO document_scan_event(document_id, scanned_by_user_id, status, engine, result_text, scanned_at)
             VALUES ($1, $2, $3, $4, $5, NOW())`,
            [insert.rows[0].id, req.user.sub, "clean", "clamav", item.scan.raw]
          );

          insertedRows.push(insert.rows[0]);
        }

        return insertedRows;
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "CASE_ATTACHMENT_UPLOADED",
        entityType: "case",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
        metadata: {
          count: result.length,
          documentIds: result.map((item) => item.id),
          files: analyzedFiles.map((item) => ({
            file_name: item.file.originalname,
            sha256: item.sha256,
          })),
        },
      });

      res.status(201).json({
        ok: true,
        uploaded: result,
        count: result.length,
      });
    } catch (err) {
      next(err);
    }
  }
);

router.post(
  "/:id/add-to-agenda",
  requireMfa,
  requirePermission("agenda.write", ["admin", "chair", "secretary"]),
  validate(addToAgendaSchema),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const caseRow = await db.query(
          `SELECT id, case_number, title, summary, committee_id
             FROM br_case
            WHERE id = $1
              AND deleted_at IS NULL`,
          [req.params.id]
        );
        const currentCase = caseRow.rows[0];
        if (!currentCase) return null;

        const meetingRow = await db.query(
          `SELECT id, meeting_number, title, committee_id
             FROM meeting
            WHERE id = $1`,
          [req.body.meeting_id]
        );
        const meeting = meetingRow.rows[0];
        if (!meeting) {
          throw Object.assign(new Error("Sitzung nicht gefunden"), {
            status: 400,
            publicMessage: "Sitzung nicht gefunden",
          });
        }

        if (!isMeetingOpen(meeting)) {
          throw Object.assign(new Error("Geschlossene Sitzungen können nicht ausgewählt werden"), {
            status: 400,
            publicMessage: "Geschlossene Sitzungen können nicht ausgewählt werden",
          });
        }

        if (currentCase.committee_id && meeting.committee_id && Number(currentCase.committee_id) !== Number(meeting.committee_id)) {
          throw Object.assign(new Error("Vorgang und Sitzung gehören zu unterschiedlichen Ausschüssen"), {
            status: 400,
            publicMessage: "Vorgang und Sitzung gehören zu unterschiedlichen Ausschüssen",
          });
        }

        let agendaItem = null;

        if (req.body.agenda_item_id) {
          const agendaItemResult = await db.query(
            `SELECT id, meeting_id, case_id, title, description, duration_minutes
               FROM meeting_agenda_item
              WHERE id = $1`,
            [req.body.agenda_item_id]
          );
          agendaItem = agendaItemResult.rows[0];

          if (!agendaItem) {
            throw Object.assign(new Error("TOP nicht gefunden"), {
              status: 404,
              publicMessage: "TOP nicht gefunden",
            });
          }

          if (agendaItem.meeting_id) {
            throw Object.assign(new Error("TOP ist bereits einer Sitzung zugewiesen"), {
              status: 400,
              publicMessage: "TOP ist bereits einer Sitzung zugewiesen",
            });
          }

          if (agendaItem.case_id && Number(agendaItem.case_id) !== Number(currentCase.id)) {
            throw Object.assign(new Error("Der ausgewählte TOP gehört zu einem anderen Vorgang"), {
              status: 400,
              publicMessage: "Der ausgewählte TOP gehört zu einem anderen Vorgang",
            });
          }

          if (!agendaItem.case_id) {
            await db.query(`UPDATE meeting_agenda_item SET case_id = $1 WHERE id = $2`, [currentCase.id, agendaItem.id]);
            agendaItem.case_id = currentCase.id;
          }
        } else {
          agendaItem = await ensureAgendaItemForCase(db, currentCase, req.body.duration_minutes ?? null);
        }

        await db.query(
          `UPDATE meeting_agenda_item
              SET meeting_id = $1,
                  agenda_number = next_agenda_number($1),
                  duration_minutes = COALESCE($3, duration_minutes),
                  case_id = COALESCE(case_id, $4)
            WHERE id = $2`,
          [req.body.meeting_id, agendaItem.id, req.body.duration_minutes ?? null, currentCase.id]
        );

        const assignedResult = await db.query(
          `SELECT ai.*, m.meeting_number, m.title AS meeting_title
             FROM meeting_agenda_item ai
             JOIN meeting m ON m.id = ai.meeting_id
            WHERE ai.id = $1`,
          [agendaItem.id]
        );

        return assignedResult.rows[0];
      });

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

      await writeAudit({
        actorId: req.user.sub,
        action: "CASE_ADDED_TO_AGENDA",
        entityType: "case",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
        metadata: { meeting_id: req.body.meeting_id, agenda_item_id: row.id },
      });

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

async function getCaseAttachments(db, caseId) {
  const result = await db.query(
    `SELECT d.id,
            d.title,
            d.file_name,
            d.mime_type,
            d.size_bytes,
            d.uploaded_at,
            uploader.full_name AS uploaded_by_name,
            uploader.email AS uploaded_by_email
       FROM document_item d
       LEFT JOIN app_user uploader ON uploader.id = d.uploaded_by
      WHERE d.case_id = $1
        AND d.deleted_at IS NULL
      ORDER BY d.uploaded_at DESC, d.id DESC`,
    [caseId]
  );

  return result.rows;
}

router.get("/:id/attachments", async (req, res, next) => {
  try {
    const result = await withDbSession(req.user, async (db) => {
      const caseRow = await getCaseById(db, req.params.id);
      if (!caseRow) return null;

      const attachments = await getCaseAttachments(db, req.params.id);
      return attachments;
    });

    if (result === null) {
      return res.status(404).json({ error: "Nicht gefunden" });
    }

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

router.delete(
  "/:id/attachments/:attachmentId",
  requireMfa,
  requirePermission("cases.write", ["admin", "chair", "secretary", "member"]),
  async (req, res, next) => {
    try {
      const deleted = await withDbSession(req.user, async (db) => {
        const caseRow = await getCaseById(db, req.params.id);
        if (!caseRow) {
          throw Object.assign(new Error("Vorgang nicht gefunden"), {
            status: 404,
            publicMessage: "Vorgang nicht gefunden",
          });
        }

        const attachmentResult = await db.query(
          `SELECT id, case_id, file_name
             FROM document_item
            WHERE id = $1
              AND case_id = $2
              AND deleted_at IS NULL`,
          [req.params.attachmentId, req.params.id]
        );

        const attachment = attachmentResult.rows[0];
        if (!attachment) {
          throw Object.assign(new Error("Datei nicht gefunden"), {
            status: 404,
            publicMessage: "Datei nicht gefunden",
          });
        }

        await db.query(
          `UPDATE document_item
              SET deleted_at = NOW()
            WHERE id = $1`,
          [req.params.attachmentId]
        );

        return attachment;
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "CASE_ATTACHMENT_DELETED",
        entityType: "case",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
        metadata: {
          attachmentId: Number(req.params.attachmentId),
          file_name: deleted.file_name,
        },
      });

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

export default router;
