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";
import { sendMail } from "../../services/mail.js";

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

const decisionStatusSchema = z.enum(["in_bearbeitung", "rueckfrage", "abgeschlossen"]);
const voteValueSchema = z.enum(["yes", "no", "abstain"]);

const nullablePositiveInt = z.preprocess(
  (value) => {
    if (value === "" || value === null || value === undefined) return undefined;
    return value;
  },
  z.coerce.number().int().positive().optional()
);

const createSchema = z.object({
  decision_number: z.string().min(3).max(50).optional().nullable(),
  meeting_id: nullablePositiveInt,
  agenda_item_id: nullablePositiveInt,
  case_id: nullablePositiveInt,
  title: z.string().min(3).max(255),
  decision_text: z.string().min(3).max(20000),
  status: decisionStatusSchema.default("in_bearbeitung"),
  votes_yes: z.coerce.number().int().min(0).default(0),
  votes_no: z.coerce.number().int().min(0).default(0),
  votes_abstain: z.coerce.number().int().min(0).default(0),
  responsible_user_id: nullablePositiveInt,
  due_date: z.string().optional().nullable(),
});

const updateSchema = z.object({
  decision_number: z.string().min(3).max(50).optional().nullable(),
  meeting_id: nullablePositiveInt,
  agenda_item_id: nullablePositiveInt,
  case_id: nullablePositiveInt,
  title: z.string().min(3).max(255).optional(),
  decision_text: z.string().min(3).max(20000).optional(),
  status: decisionStatusSchema.optional(),
  votes_yes: z.coerce.number().int().min(0).optional(),
  votes_no: z.coerce.number().int().min(0).optional(),
  votes_abstain: z.coerce.number().int().min(0).optional(),
  responsible_user_id: nullablePositiveInt,
  due_date: z.string().optional().nullable(),
});

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

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 || value === null) return undefined;
  const trimmed = String(value).trim();
  return trimmed ? trimmed : undefined;
}

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 mapDecisionRow(row) {
  if (!row) return null;
  return {
    id: row.id,
    decision_number: row.decision_number,
    title: row.title,
    decision_text: row.decision_text,
    status: row.status,
    meeting_id: row.meeting_id,
    meeting_number: row.meeting_number,
    agenda_item_id: row.agenda_item_id,
    agenda_number: row.agenda_number,
    agenda_title: row.agenda_title,
    case_id: row.case_id,
    case_number: row.case_number,
    votes_yes: row.votes_yes,
    votes_no: row.votes_no,
    votes_abstain: row.votes_abstain,
    voting_open: row.voting_open,
    voting_started_at: row.voting_started_at,
    voting_started_by: row.voting_started_by,
    voting_closed_at: row.voting_closed_at,
    voting_closed_by: row.voting_closed_by,
    responsible_user_id: row.responsible_user_id,
    responsible_name: row.responsible_name,
    responsible_email: row.responsible_email,
    due_date: row.due_date,
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
}

async function getDecisionList(db, options = {}) {
  const includeCompleted = parseBoolean(options.includeCompleted, false);
  const result = await db.query(
    `SELECT d.id,
            d.decision_number,
            d.title,
            d.decision_text,
            d.status,
            d.meeting_id,
            m.meeting_number,
            d.agenda_item_id,
            ai.agenda_number,
            ai.title AS agenda_title,
            d.case_id,
            c.case_number,
            d.votes_yes,
            d.votes_no,
            d.votes_abstain,
            d.voting_open,
            d.voting_started_at,
            d.voting_started_by,
            d.voting_closed_at,
            d.voting_closed_by,
            d.responsible_user_id,
            u.full_name AS responsible_name,
            u.email AS responsible_email,
            d.due_date,
            d.created_at,
            d.updated_at
       FROM decision d
       LEFT JOIN meeting m ON m.id = d.meeting_id
       LEFT JOIN meeting_agenda_item ai ON ai.id = d.agenda_item_id
       LEFT JOIN br_case c ON c.id = d.case_id
       LEFT JOIN app_user u ON u.id = d.responsible_user_id
      WHERE ($1::boolean = TRUE OR d.status <> 'abgeschlossen')
      ORDER BY d.created_at DESC, d.id DESC`,
    [includeCompleted]
  );

  return result.rows.map(mapDecisionRow);
}

async function getDecisionById(db, id) {
  const result = await db.query(
    `SELECT d.id,
            d.decision_number,
            d.title,
            d.decision_text,
            d.status,
            d.meeting_id,
            m.meeting_number,
            d.agenda_item_id,
            ai.agenda_number,
            ai.title AS agenda_title,
            d.case_id,
            c.case_number,
            d.votes_yes,
            d.votes_no,
            d.votes_abstain,
            d.voting_open,
            d.voting_started_at,
            d.voting_started_by,
            d.voting_closed_at,
            d.voting_closed_by,
            d.responsible_user_id,
            u.full_name AS responsible_name,
            u.email AS responsible_email,
            d.due_date,
            d.created_at,
            d.updated_at
       FROM decision d
       LEFT JOIN meeting m ON m.id = d.meeting_id
       LEFT JOIN meeting_agenda_item ai ON ai.id = d.agenda_item_id
       LEFT JOIN br_case c ON c.id = d.case_id
       LEFT JOIN app_user u ON u.id = d.responsible_user_id
      WHERE d.id = $1`,
    [id]
  );

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

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

async function deriveFromAgendaItem(db, agendaItemId) {
  if (agendaItemId == null) return null;

  const result = await db.query(
    `SELECT ai.id,
            ai.meeting_id,
            ai.case_id,
            ai.title,
            ai.agenda_number,
            m.status AS meeting_status,
            c.case_number
       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
      WHERE ai.id = $1`,
    [agendaItemId]
  );

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

  if (!row.meeting_id) {
    throw Object.assign(new Error("Der ausgewählte TOP ist keiner Sitzung zugeordnet"), {
      status: 400,
      publicMessage: "Der ausgewählte TOP ist keiner Sitzung zugeordnet",
    });
  }

  return row;
}

async function getCaseForRueckfrage(db, caseId) {
  if (!caseId) return null;

  const result = await db.query(
    `SELECT id, case_number, title, summary
       FROM br_case
      WHERE id = $1
        AND deleted_at IS NULL`,
    [caseId]
  );

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

async function getUnassignedAgendaItemForCase(db, caseId) {
  if (!caseId) return null;

  const result = await db.query(
    `SELECT id
       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 ensureRueckfrageAgendaItem(db, decisionRow) {
  if (!decisionRow?.case_id) return null;

  const existing = await getUnassignedAgendaItemForCase(db, decisionRow.case_id);
  if (existing) return existing;

  const caseRow = await getCaseForRueckfrage(db, decisionRow.case_id);
  if (!caseRow) return null;

  const title = `${caseRow.case_number} – ${caseRow.title}`;
  const description = `Automatisch aus Rückfrage zu Beschluss ${decisionRow.decision_number} erzeugt.`;

  const insert = await db.query(
    `INSERT INTO meeting_agenda_item(meeting_id, agenda_number, title, description, duration_minutes, case_id)
     VALUES (NULL, NULL, $1, $2, NULL, $3)
     RETURNING id`,
    [title, description, caseRow.id]
  );

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

async function applyRueckfrageWorkflow(db, decisionRow, actorId) {
  if (!decisionRow || decisionRow.status !== "rueckfrage") return;

  if (decisionRow.case_id) {
    await db.query(
      `UPDATE br_case
          SET status = 'rueckfrage',
              updated_by = $2,
              updated_at = NOW()
        WHERE id = $1
          AND deleted_at IS NULL`,
      [decisionRow.case_id, actorId]
    );

    await ensureRueckfrageAgendaItem(db, decisionRow);
  }

  if (decisionRow.responsible_user_id) {
    await db.query(
      `INSERT INTO notification_item(user_id, notification_type, title, entity_type, entity_id)
       VALUES ($1, $2, $3, $4, $5)`,
      [
        decisionRow.responsible_user_id,
        'decision_rueckfrage',
        `Rückfrage zu Beschluss ${decisionRow.decision_number}`,
        'decision',
        decisionRow.id,
      ]
    );
  }
}

async function sendRueckfrageMail(decisionRow) {
  if (!decisionRow?.responsible_email) return;

  const subject = `Rückfrage zu Beschluss ${decisionRow.decision_number}`;
  const lines = [
    `Hallo ${decisionRow.responsible_name || 'Kollegin/Kollege'},`,
    '',
    `zu folgendem Beschluss wurde eine Rückfrage markiert:`,
    `${decisionRow.decision_number} – ${decisionRow.title}`,
  ];

  if (decisionRow.case_number) {
    lines.push(`Vorgang: ${decisionRow.case_number}`);
  }

  lines.push('', 'Beschlusstext:', decisionRow.decision_text || '', '', 'Bitte Rückmeldung im BR-Office eintragen.');

  await sendMail({
    to: decisionRow.responsible_email,
    subject,
    text: lines.join('\n'),
  });
}

function voteCountsFromRows(rows) {
  return rows.reduce(
    (acc, row) => {
      if (row.vote_value === "yes") acc.yes += Number(row.count || 0);
      if (row.vote_value === "no") acc.no += Number(row.count || 0);
      if (row.vote_value === "abstain") acc.abstain += Number(row.count || 0);
      return acc;
    },
    { yes: 0, no: 0, abstain: 0 }
  );
}

function mapVoteToDecisionColumns(counts) {
  return {
    votes_yes: counts.yes || 0,
    votes_no: counts.no || 0,
    votes_abstain: counts.abstain || 0,
  };
}

async function synchronizeDecisionVoteCounts(db, decisionId) {
  const result = await db.query(`SELECT * FROM sync_decision_vote_counts($1)`, [decisionId]);
  const row = result.rows[0] || {};
  return {
    yes: Number(row.votes_yes || 0),
    no: Number(row.votes_no || 0),
    abstain: Number(row.votes_abstain || 0),
  };
}

async function getVotingContext(db, decisionId) {
  const result = await db.query(
    `SELECT d.id,
            d.decision_number,
            d.title,
            d.voting_open,
            d.voting_started_at,
            d.voting_started_by,
            d.voting_closed_at,
            d.voting_closed_by,
            d.meeting_id,
            m.meeting_number,
            m.title AS meeting_title,
            m.status AS meeting_status,
            m.committee_id,
            c.name AS committee_name
       FROM decision d
       LEFT JOIN meeting m ON m.id = d.meeting_id
       LEFT JOIN committee c ON c.id = m.committee_id
      WHERE d.id = $1`,
    [decisionId]
  );

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

async function getEligibleVoters(db, committeeId, decisionId) {
  if (!committeeId) return [];

  const result = await db.query(
    `SELECT u.id,
            u.full_name,
            u.email,
            u.role,
            v.vote_value,
            v.updated_at AS voted_at
       FROM committee_member cm
       JOIN app_user u ON u.id = cm.user_id
       LEFT JOIN decision_vote v ON v.decision_id = $2 AND v.user_id = u.id
      WHERE cm.committee_id = $1
        AND u.is_active = TRUE
        AND u.role <> 'advisor'
      ORDER BY u.full_name ASC, u.id ASC`,
    [committeeId, decisionId]
  );

  return result.rows;
}

async function canUserVoteOnDecision(db, decisionId, userId) {
  const result = await db.query(
    `SELECT 1
       FROM decision d
       JOIN meeting m ON m.id = d.meeting_id
       JOIN committee_member cm ON cm.committee_id = m.committee_id
       JOIN app_user u ON u.id = cm.user_id
      WHERE d.id = $1
        AND cm.user_id = $2
        AND u.is_active = TRUE
        AND u.role <> 'advisor'
      LIMIT 1`,
    [decisionId, userId]
  );

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

async function userCanManageDecisionVoting(db) {
  const result = await db.query(`SELECT app_has_permission('decisions.vote.manage') AS allowed`);
  return Boolean(result.rows[0]?.allowed);
}

async function getDecisionVotingState(db, decisionId, userId) {
  await synchronizeDecisionVoteCounts(db, decisionId);

  const context = await getVotingContext(db, decisionId);
  if (!context) return null;

  const countsResult = await db.query(
    `SELECT vote_value, COUNT(*)::int AS count
       FROM decision_vote
      WHERE decision_id = $1
      GROUP BY vote_value`,
    [decisionId]
  );
  const ownVoteResult = await db.query(
    `SELECT vote_value, updated_at
       FROM decision_vote
      WHERE decision_id = $1
        AND user_id = $2`,
    [decisionId, userId]
  );
  const eligibleVoters = await getEligibleVoters(db, context.committee_id, decisionId);
  const canVote = await canUserVoteOnDecision(db, decisionId, userId);
  const canManage = await userCanManageDecisionVoting(db);

  const counts = voteCountsFromRows(countsResult.rows);
  const columns = mapVoteToDecisionColumns(counts);

  return {
    decision: {
      id: context.id,
      decision_number: context.decision_number,
      title: context.title,
      meeting_id: context.meeting_id,
      meeting_number: context.meeting_number,
      meeting_title: context.meeting_title,
      meeting_status: context.meeting_status,
      committee_id: context.committee_id,
      committee_name: context.committee_name,
      voting_open: context.voting_open,
      voting_started_at: context.voting_started_at,
      voting_started_by: context.voting_started_by,
      voting_closed_at: context.voting_closed_at,
      voting_closed_by: context.voting_closed_by,
      ...columns,
    },
    counts,
    total_votes: counts.yes + counts.no + counts.abstain,
    eligible_voter_count: eligibleVoters.length,
    eligible_voters: eligibleVoters.map((row) => ({
      id: row.id,
      full_name: row.full_name,
      email: row.email,
      role: row.role,
      vote_value: row.vote_value || null,
      voted_at: row.voted_at || null,
      has_voted: Boolean(row.vote_value),
    })),
    current_user_vote: ownVoteResult.rows[0]?.vote_value || null,
    current_user_voted_at: ownVoteResult.rows[0]?.updated_at || null,
    current_user_can_vote: canVote && context.meeting_status === "gestartet",
    can_manage: canManage,
  };
}

router.get("/meta", async (req, res, next) => {
  try {
    const data = await withDbSession(req.user, async (db) => {
      const [agendaItems, users] = await Promise.all([
        db.query(
          `SELECT ai.id,
                  ai.agenda_number,
                  ai.title,
                  ai.meeting_id,
                  ai.case_id,
                  m.committee_id,
                  m.meeting_number,
                  m.status AS meeting_status,
                  c.case_number
             FROM meeting_agenda_item ai
             JOIN meeting m ON m.id = ai.meeting_id
             LEFT JOIN br_case c ON c.id = ai.case_id
            WHERE ai.meeting_id IS NOT NULL
              AND m.status = 'gestartet'
            ORDER BY m.meeting_date DESC,
                     COALESCE(NULLIF(regexp_replace(COALESCE(ai.agenda_number, ''), '[^0-9]', '', 'g'), ''), '999999')::int,
                     ai.id`
        ),
        db.query(`SELECT u.id,
                         u.full_name,
                         u.email,
                         u.role,
                         u.committee_id,
                         COALESCE(ARRAY_AGG(cm.committee_id ORDER BY cm.committee_id) FILTER (WHERE cm.committee_id IS NOT NULL), ARRAY[]::BIGINT[]) AS committee_ids
                    FROM app_user u
                    LEFT JOIN committee_member cm ON cm.user_id = u.id
                   WHERE u.is_active = TRUE
                   GROUP BY u.id
                   ORDER BY u.full_name`),
      ]);

      return { agendaItems: agendaItems.rows, users: users.rows };
    });

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

router.get("/", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, (db) =>
      getDecisionList(db, { includeCompleted: req.query.include_completed })
    );
    res.json(rows);
  } catch (err) {
    next(err);
  }
});

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

router.post(
  "/:id/voting/start",
  requireMfa,
  requirePermission("decisions.vote.manage", ["admin", "chair", "secretary"]),
  async (req, res, next) => {
    try {
      const state = await withDbSession(req.user, async (db) => {
        const context = await getVotingContext(db, req.params.id);
        if (!context) return null;
        if (!context.meeting_id) {
          throw Object.assign(new Error("Für eine Abstimmung muss der Beschluss einer Sitzung zugeordnet sein."), {
            status: 400,
            publicMessage: "Für eine Abstimmung muss der Beschluss einer Sitzung zugeordnet sein.",
          });
        }
        if (!context.committee_id) {
          throw Object.assign(new Error("Die Sitzung hat kein Gremium/Ausschuss zugeordnet."), {
            status: 400,
            publicMessage: "Die Sitzung hat kein Gremium/Ausschuss zugeordnet.",
          });
        }
        if (context.meeting_status !== "gestartet") {
          throw Object.assign(new Error("Abstimmungen sind nur in gestarteten Sitzungen möglich."), {
            status: 400,
            publicMessage: "Abstimmungen sind nur in gestarteten Sitzungen möglich.",
          });
        }

        await db.query(
          `UPDATE decision
              SET voting_open = TRUE,
                  voting_started_at = COALESCE(voting_started_at, NOW()),
                  voting_started_by = COALESCE(voting_started_by, $2),
                  voting_closed_at = NULL,
                  voting_closed_by = NULL,
                  updated_by = $2,
                  updated_at = NOW()
            WHERE id = $1`,
          [req.params.id, req.user.sub]
        );

        return getDecisionVotingState(db, req.params.id, req.user.sub);
      });

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

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

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

router.post(
  "/:id/voting/close",
  requireMfa,
  requirePermission("decisions.vote.manage", ["admin", "chair", "secretary"]),
  async (req, res, next) => {
    try {
      const state = await withDbSession(req.user, async (db) => {
        const context = await getVotingContext(db, req.params.id);
        if (!context) return null;

        await synchronizeDecisionVoteCounts(db, req.params.id);
        await db.query(
          `UPDATE decision
              SET voting_open = FALSE,
                  voting_closed_at = NOW(),
                  voting_closed_by = $2,
                  updated_by = $2,
                  updated_at = NOW()
            WHERE id = $1`,
          [req.params.id, req.user.sub]
        );

        return getDecisionVotingState(db, req.params.id, req.user.sub);
      });

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

      await writeAudit({
        actorId: req.user.sub,
        action: "DECISION_VOTING_CLOSED",
        entityType: "decision",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
        metadata: { counts: state.counts },
      });

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

router.put("/:id/vote", validate(z.object({ vote_value: voteValueSchema })), async (req, res, next) => {
  try {
    const state = await withDbSession(req.user, async (db) => {
      const context = await getVotingContext(db, req.params.id);
      if (!context) return null;
      if (!context.voting_open) {
        throw Object.assign(new Error("Die Abstimmung ist nicht geöffnet."), {
          status: 400,
          publicMessage: "Die Abstimmung ist nicht geöffnet.",
        });
      }
      if (context.meeting_status !== "gestartet") {
        throw Object.assign(new Error("Abstimmungen sind nur in gestarteten Sitzungen möglich."), {
          status: 400,
          publicMessage: "Abstimmungen sind nur in gestarteten Sitzungen möglich.",
        });
      }
      if (!context.committee_id) {
        throw Object.assign(new Error("Die Sitzung hat kein Gremium/Ausschuss zugeordnet."), {
          status: 400,
          publicMessage: "Die Sitzung hat kein Gremium/Ausschuss zugeordnet.",
        });
      }

      const canVote = await canUserVoteOnDecision(db, req.params.id, req.user.sub);
      if (!canVote) {
        throw Object.assign(new Error("Keine Stimmberechtigung für dieses Gremium."), {
          status: 403,
          publicMessage: "Keine Stimmberechtigung für dieses Gremium.",
        });
      }

      await db.query(
        `INSERT INTO decision_vote(decision_id, user_id, vote_value)
         VALUES ($1, $2, $3)
         ON CONFLICT (decision_id, user_id)
         DO UPDATE SET vote_value = EXCLUDED.vote_value,
                       updated_at = NOW()`,
        [req.params.id, req.user.sub, req.body.vote_value]
      );

      await synchronizeDecisionVoteCounts(db, req.params.id);
      return getDecisionVotingState(db, req.params.id, req.user.sub);
    });

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

    await writeAudit({
      actorId: req.user.sub,
      action: "DECISION_VOTE_CAST",
      entityType: "decision",
      entityId: Number(req.params.id),
      ipAddress: req.ip,
      metadata: { vote_value: req.body.vote_value },
    });

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

router.get("/:id", async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, (db) => getDecisionById(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("decisions.write", ["admin", "chair", "secretary", "member"]),
  validate(createSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        decision_number: normalizeOptionalText(req.body.decision_number),
        title: req.body.title.trim(),
        decision_text: req.body.decision_text.trim(),
        meeting_id: normalizeNullableNumber(req.body.meeting_id),
        agenda_item_id: normalizeNullableNumber(req.body.agenda_item_id),
        case_id: normalizeNullableNumber(req.body.case_id),
        responsible_user_id: normalizeNullableNumber(req.body.responsible_user_id),
        due_date: normalizeNullableText(req.body.due_date),
      };

      const becameRueckfrage = payload.status === "rueckfrage";

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

        if (payload.agenda_item_id) {
          const agenda = await deriveFromAgendaItem(db, payload.agenda_item_id);
          payload.meeting_id = Number(agenda.meeting_id);
          payload.case_id = agenda.case_id ? Number(agenda.case_id) : null;
        }

        const result = await db.query(
          `INSERT INTO decision(
             decision_number,
             meeting_id,
             agenda_item_id,
             case_id,
             title,
             decision_text,
             status,
             votes_yes,
             votes_no,
             votes_abstain,
             responsible_user_id,
             due_date,
             created_by,
             updated_by
           ) VALUES (
             COALESCE(NULLIF($1, ''), next_decision_number()),
             $2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$13
           ) RETURNING id`,
          [
            payload.decision_number ?? "",
            payload.meeting_id,
            payload.agenda_item_id,
            payload.case_id,
            payload.title,
            payload.decision_text,
            payload.status,
            payload.votes_yes ?? 0,
            payload.votes_no ?? 0,
            payload.votes_abstain ?? 0,
            payload.responsible_user_id,
            payload.due_date,
            req.user.sub,
          ]
        );

        const row = await getDecisionById(db, result.rows[0].id);
        if (becameRueckfrage) {
          await applyRueckfrageWorkflow(db, row, req.user.sub);
        }

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

      if (becameRueckfrage) {
        sendRueckfrageMail(row).catch((error) => {
          console.error("[mail] rueckfrage email failed", error);
        });
      }

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

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

router.put(
  "/:id",
  requireMfa,
  requirePermission("decisions.write", ["admin", "chair", "secretary", "member"]),
  validate(updateSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        decision_number: normalizeOptionalText(req.body.decision_number),
        title: req.body.title !== undefined ? req.body.title.trim() : undefined,
        decision_text: req.body.decision_text !== undefined ? req.body.decision_text.trim() : undefined,
        meeting_id: normalizeNullableNumber(req.body.meeting_id),
        agenda_item_id: normalizeNullableNumber(req.body.agenda_item_id),
        case_id: normalizeNullableNumber(req.body.case_id),
        responsible_user_id: normalizeNullableNumber(req.body.responsible_user_id),
        due_date: normalizeNullableText(req.body.due_date),
      };

      let becameRueckfrage = false;

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

        const nextStatus = payload.status ?? existing.status;
        becameRueckfrage = nextStatus === "rueckfrage" && existing.status !== "rueckfrage";

        await assertResponsibleUser(db, payload.responsible_user_id);

        if (payload.agenda_item_id) {
          const agenda = await deriveFromAgendaItem(db, payload.agenda_item_id);
          payload.meeting_id = Number(agenda.meeting_id);
          payload.case_id = agenda.case_id ? Number(agenda.case_id) : null;
        }

        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 [
          ["decision_number", payload.decision_number],
          ["meeting_id", payload.meeting_id],
          ["agenda_item_id", payload.agenda_item_id],
          ["case_id", payload.case_id],
          ["title", payload.title],
          ["decision_text", payload.decision_text],
          ["status", payload.status],
          ["votes_yes", payload.votes_yes],
          ["votes_no", payload.votes_no],
          ["votes_abstain", payload.votes_abstain],
          ["responsible_user_id", payload.responsible_user_id],
          ["due_date", payload.due_date],
        ]) {
          if (value !== undefined) push(column, value);
        }

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

        push("updated_by", req.user.sub);
        values.push(req.params.id);

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

        if (becameRueckfrage) {
          await applyRueckfrageWorkflow(db, row, req.user.sub);
          row = await getDecisionById(db, req.params.id);
        }

        return row;
      });

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

      if (becameRueckfrage) {
        sendRueckfrageMail(row).catch((error) => {
          console.error("[mail] rueckfrage email failed", error);
        });
      }

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

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

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

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

export default router;
