import express from "express";
import { withDbSession } from "../../config/db.js";
import { authenticate, requireMfa } from "../../middleware/auth.js";
import { requirePermission, requireRoles } from "../../middleware/rbac.js";
import { validate } from "../../middleware/validate.js";
import { writeAudit } from "../../utils/audit.js";
import { taskSchema, taskUpdateSchema } from "./schemas.js";

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

function asId(value) {
  if (value === undefined || value === null || value === "") return null;
  const n = Number(value);
  return Number.isInteger(n) && n > 0 ? n : null;
}

const referenceTables = {
  app_user: "app_user",
  committee: "committee",
  br_case: "br_case",
  meeting: "meeting",
};

async function ensureExists(db, tableKey, id, label, where = "") {
  if (!id) return null;

  const table = referenceTables[tableKey];
  if (!table) throw new Error(`Ungültige Referenztabelle: ${tableKey}`);

  const result = await db.query(`SELECT id FROM ${table} WHERE id = $1 ${where}`, [id]);
  if (!result.rows[0]) {
    throw Object.assign(new Error(`${label} nicht gefunden`), {
      status: 400,
      publicMessage: `${label} nicht gefunden`,
    });
  }
  return result.rows[0];
}

async function getAgendaItemById(db, id) {
  if (!id) return null;
  const result = await db.query(
    `SELECT ai.id,
            ai.title,
            ai.description,
            ai.duration_minutes,
            ai.case_id,
            ai.meeting_id,
            latest_decision.id AS decision_id
       FROM meeting_agenda_item ai
       LEFT JOIN LATERAL (
         SELECT d.id
           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`,
    [id]
  );
  return result.rows[0] || null;
}

async function getDecisionById(db, id) {
  if (!id) return null;
  const result = await db.query(
    `SELECT id, title, case_id, meeting_id, agenda_item_id
       FROM decision
      WHERE id = $1`,
    [id]
  );
  return result.rows[0] || null;
}

async function getTaskById(db, id) {
  const result = await db.query(`SELECT * FROM task WHERE id = $1 AND deleted_at IS NULL`, [id]);
  return result.rows[0] || null;
}

async function validateReferences(db, payload) {
  const assignedUserId = asId(payload.assigned_user_id);
  const backupUserId = asId(payload.backup_user_id);
  const committeeId = asId(payload.committee_id);
  const caseId = asId(payload.case_id);
  const meetingId = asId(payload.meeting_id);
  const agendaItemId = asId(payload.agenda_item_id);
  const decisionId = asId(payload.decision_id);

  await ensureExists(db, "app_user", assignedUserId, "Zugewiesener Benutzer", "AND is_active = TRUE");
  await ensureExists(db, "app_user", backupUserId, "Stellvertretung", "AND is_active = TRUE");
  await ensureExists(db, "committee", committeeId, "Gremium");
  await ensureExists(db, "br_case", caseId, "Vorgang", "AND deleted_at IS NULL");
  await ensureExists(db, "meeting", meetingId, "Sitzung");

  const agendaItem = await getAgendaItemById(db, agendaItemId);
  if (agendaItemId && !agendaItem) {
    throw Object.assign(new Error("TOP nicht gefunden"), {
      status: 400,
      publicMessage: "TOP nicht gefunden",
    });
  }

  const decision = await getDecisionById(db, decisionId);
  if (decisionId && !decision) {
    throw Object.assign(new Error("Beschluss nicht gefunden"), {
      status: 400,
      publicMessage: "Beschluss nicht gefunden",
    });
  }

  if (agendaItem && meetingId && agendaItem.meeting_id && Number(agendaItem.meeting_id) !== meetingId) {
    throw Object.assign(new Error("TOP gehört nicht zur gewählten Sitzung"), {
      status: 400,
      publicMessage: "TOP gehört nicht zur gewählten Sitzung",
    });
  }

  if (agendaItem && caseId && agendaItem.case_id && Number(agendaItem.case_id) !== caseId) {
    throw Object.assign(new Error("TOP gehört nicht zum gewählten Vorgang"), {
      status: 400,
      publicMessage: "TOP gehört nicht zum gewählten Vorgang",
    });
  }

  if (decision && meetingId && decision.meeting_id && Number(decision.meeting_id) !== meetingId) {
    throw Object.assign(new Error("Beschluss gehört nicht zur gewählten Sitzung"), {
      status: 400,
      publicMessage: "Beschluss gehört nicht zur gewählten Sitzung",
    });
  }

  if (decision && caseId && decision.case_id && Number(decision.case_id) !== caseId) {
    throw Object.assign(new Error("Beschluss gehört nicht zum gewählten Vorgang"), {
      status: 400,
      publicMessage: "Beschluss gehört nicht zum gewählten Vorgang",
    });
  }

  if (
    decision &&
    agendaItem &&
    decision.agenda_item_id &&
    Number(decision.agenda_item_id) !== Number(agendaItem.id)
  ) {
    throw Object.assign(new Error("Beschluss gehört nicht zum gewählten TOP"), {
      status: 400,
      publicMessage: "Beschluss gehört nicht zum gewählten TOP",
    });
  }

  return { agendaItem, decision };
}

function buildResolvedPayload(payload, context = {}) {
  const agendaItem = context.agendaItem || null;
  const decision = context.decision || null;

  const resolvedCaseId =
    asId(payload.case_id) ??
    asId(agendaItem?.case_id) ??
    asId(decision?.case_id) ??
    null;

  const resolvedMeetingId =
    asId(payload.meeting_id) ??
    asId(agendaItem?.meeting_id) ??
    asId(decision?.meeting_id) ??
    null;

  const resolvedAgendaItemId =
    asId(payload.agenda_item_id) ??
    asId(decision?.agenda_item_id) ??
    null;

  const resolvedDecisionId =
    asId(payload.decision_id) ??
    asId(agendaItem?.decision_id) ??
    null;

  return {
    title: payload.title?.trim() || "",
    description: payload.description?.trim() || null,
    status: payload.status || "offen",
    priority: payload.priority || "mittel",
    assigned_user_id: asId(payload.assigned_user_id),
    backup_user_id: asId(payload.backup_user_id),
    committee_id: asId(payload.committee_id),
    case_id: resolvedCaseId,
    meeting_id: resolvedMeetingId,
    agenda_item_id: resolvedAgendaItemId,
    decision_id: resolvedDecisionId,
    due_date: payload.due_date || null,
    confidentiality_level: payload.confidentiality_level || "intern",
  };
}

router.get("/", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `SELECT t.*,
                u.full_name AS assigned_user_name,
                bu.full_name AS backup_user_name,
                c.name AS committee_name,
                bc.case_number,
                bc.title AS case_title,
                m.meeting_number,
                ai.title AS agenda_item_title,
                ai.agenda_number,
                d.title AS decision_title,
                d.decision_number
           FROM task t
           LEFT JOIN app_user u ON u.id = t.assigned_user_id
           LEFT JOIN app_user bu ON bu.id = t.backup_user_id
           LEFT JOIN committee c ON c.id = t.committee_id
           LEFT JOIN br_case bc ON bc.id = t.case_id
           LEFT JOIN meeting m ON m.id = t.meeting_id
           LEFT JOIN meeting_agenda_item ai ON ai.id = t.agenda_item_id
           LEFT JOIN decision d ON d.id = t.decision_id
          WHERE t.deleted_at IS NULL
          ORDER BY t.id DESC`
      );
      return result.rows;
    });

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

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

router.post(
  "/",
  requireMfa,
  requirePermission("tasks.write", ["admin", "chair", "secretary", "member"]),
  validate(taskSchema),
  async (req, res, next) => {
    try {
      const created = await withDbSession(req.user, async (db) => {
        const refs = await validateReferences(db, req.body);
        const payload = buildResolvedPayload(req.body, refs);

        const result = await db.query(
          `INSERT INTO task (
             title,
             description,
             status,
             priority,
             assigned_user_id,
             backup_user_id,
             committee_id,
             case_id,
             meeting_id,
             agenda_item_id,
             decision_id,
             due_date,
             confidentiality_level,
             created_by,
             updated_by
           )
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$14)
           RETURNING *`,
          [
            payload.title,
            payload.description,
            payload.status,
            payload.priority,
            payload.assigned_user_id,
            payload.backup_user_id,
            payload.committee_id,
            payload.case_id,
            payload.meeting_id,
            payload.agenda_item_id,
            payload.decision_id,
            payload.due_date,
            payload.confidentiality_level,
            req.user.sub,
          ]
        );

        return result.rows[0];
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "TASK_CREATED",
        entityType: "task",
        entityId: created.id,
        ipAddress: req.ip,
        metadata: { payload: req.body },
      });

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

router.put(
  "/:id",
  requireMfa,
  requirePermission("tasks.write", ["admin", "chair", "secretary", "member"]),
  validate(taskUpdateSchema),
  async (req, res, next) => {
    try {
      const updated = await withDbSession(req.user, async (db) => {
        const id = Number(req.params.id);
        const current = await getTaskById(db, id);

        if (!current) return null;

        const merged = {
          ...current,
          ...req.body,
        };

        const refs = await validateReferences(db, merged);
        const payload = buildResolvedPayload(merged, refs);

        const result = await db.query(
          `UPDATE task
              SET title = $2,
                  description = $3,
                  status = $4,
                  priority = $5,
                  assigned_user_id = $6,
                  backup_user_id = $7,
                  committee_id = $8,
                  case_id = $9,
                  meeting_id = $10,
                  agenda_item_id = $11,
                  decision_id = $12,
                  due_date = $13,
                  confidentiality_level = $14,
                  updated_by = $15,
                  updated_at = NOW()
            WHERE id = $1
              AND deleted_at IS NULL
            RETURNING *`,
          [
            id,
            payload.title,
            payload.description,
            payload.status,
            payload.priority,
            payload.assigned_user_id,
            payload.backup_user_id,
            payload.committee_id,
            payload.case_id,
            payload.meeting_id,
            payload.agenda_item_id,
            payload.decision_id,
            payload.due_date,
            payload.confidentiality_level,
            req.user.sub,
          ]
        );

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

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

      await writeAudit({
        actorId: req.user.sub,
        action: "TASK_UPDATED",
        entityType: "task",
        entityId: updated.id,
        ipAddress: req.ip,
        metadata: { changes: req.body },
      });

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

router.delete(
  "/:id",
  requireMfa,
  requirePermission("tasks.write", ["admin", "chair", "secretary", "member"]),
  async (req, res, next) => {
    try {
      const deleted = await withDbSession(req.user, async (db) => {
        const result = await db.query(
          `UPDATE task
              SET deleted_at = NOW(),
                  updated_by = $2,
                  updated_at = NOW()
            WHERE id = $1
              AND deleted_at IS NULL
            RETURNING *`,
          [req.params.id, req.user.sub]
        );
        return result.rows[0] || null;
      });

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

      await writeAudit({
        actorId: req.user.sub,
        action: "TASK_DELETED",
        entityType: "task",
        entityId: deleted.id,
        ipAddress: req.ip,
      });

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

export default router;
