import express from "express";
import argon2 from "argon2";
import jwt from "jsonwebtoken";
import { z } from "zod";
import { authenticate, requireMfa } from "../../middleware/auth.js";
import { requirePermission } from "../../middleware/rbac.js";
import { validate } from "../../middleware/validate.js";
import { pool, query, withDbSession } from "../../config/db.js";
import { env } from "../../config/env.js";
import { writeAudit } from "../../utils/audit.js";

export const publicKummerkastenRouter = express.Router();
const adminRouter = express.Router();

const PUBLIC_TOKEN_SCOPE = "kummerkasten";
const PUBLIC_TOKEN_EXPIRES_IN = "4h";

const settingsSchema = z.object({
  enabled: z.boolean().optional(),
  access_password: z.string().max(200).optional().or(z.literal("")),
  clear_password: z.boolean().optional(),
  intro_text: z.string().max(4000).optional().nullable(),
  target_committee_id: z.coerce.number().int().positive().optional().nullable(),
});

const accessSchema = z.object({
  password: z.string().min(1).max(200),
});

const submitSchema = z.object({
  public_access_token: z.string().min(10),
  title: z.string().min(3).max(220),
  description: z.string().min(3).max(8000),
  submitter_name: z.string().max(255).optional().nullable(),
  submitter_contact: z.string().max(255).optional().nullable(),
});

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

function shorten(value, maxLength) {
  const text = String(value || "").trim();
  if (text.length <= maxLength) return text;
  return `${text.slice(0, Math.max(0, maxLength - 1))}…`;
}

function signPublicToken() {
  return jwt.sign(
    { scope: PUBLIC_TOKEN_SCOPE, setting_id: 1 },
    env.jwtAccessSecret,
    { expiresIn: PUBLIC_TOKEN_EXPIRES_IN }
  );
}

function verifyPublicToken(token) {
  try {
    const decoded = jwt.verify(token, env.jwtAccessSecret);
    return decoded?.scope === PUBLIC_TOKEN_SCOPE && Number(decoded?.setting_id) === 1;
  } catch {
    return false;
  }
}

async function getSetting(db = null) {
  const executor = db || { query };
  const result = await executor.query(
    `SELECT s.id,
            s.enabled,
            s.access_password_hash,
            s.intro_text,
            s.target_committee_id,
            c.name AS target_committee_name,
            s.updated_at
       FROM kummerkasten_setting s
       LEFT JOIN committee c ON c.id = s.target_committee_id
      WHERE s.id = 1`
  );
  return result.rows[0] || null;
}

function publicStatus(setting) {
  return {
    enabled: Boolean(setting?.enabled),
    password_required: Boolean(setting?.access_password_hash),
    intro_text: setting?.intro_text || "Reichen Sie hier vertraulich einen Vorschlag oder ein Anliegen für die Tagesordnung ein.",
    target_committee_name: setting?.target_committee_name || null,
  };
}

function adminStatus(setting) {
  return {
    enabled: Boolean(setting?.enabled),
    password_set: Boolean(setting?.access_password_hash),
    intro_text: setting?.intro_text || "",
    target_committee_id: setting?.target_committee_id || "",
    target_committee_name: setting?.target_committee_name || null,
    updated_at: setting?.updated_at || null,
  };
}

function buildAgendaDescription({ description, submitterName, submitterContact, targetCommitteeName }) {
  const parts = [
    `Kummerkasten-Einreichung vom ${new Date().toLocaleDateString("de-DE")}`,
  ];

  if (targetCommitteeName) parts.push(`Ziel-Gremium: ${targetCommitteeName}`);
  if (submitterName) parts.push(`Name: ${submitterName}`);
  if (submitterContact) parts.push(`Kontakt: ${submitterContact}`);

  parts.push("", "Beschreibung:", description);

  return parts.join("\n");
}

publicKummerkastenRouter.get("/status", async (req, res, next) => {
  try {
    const setting = await getSetting();
    res.json(publicStatus(setting));
  } catch (err) {
    next(err);
  }
});

publicKummerkastenRouter.post("/access", validate(accessSchema), async (req, res, next) => {
  try {
    const setting = await getSetting();
    if (!setting?.enabled) {
      return res.status(404).json({ error: "Der Kummerkasten ist derzeit nicht aktiv." });
    }
    if (!setting.access_password_hash) {
      return res.status(403).json({ error: "Für den Kummerkasten ist noch kein Kennwort gesetzt." });
    }

    const ok = await argon2.verify(setting.access_password_hash, req.body.password);
    if (!ok) {
      return res.status(401).json({ error: "Kennwort ist nicht korrekt." });
    }

    res.json({ accessToken: signPublicToken(), expiresIn: PUBLIC_TOKEN_EXPIRES_IN });
  } catch (err) {
    next(err);
  }
});

publicKummerkastenRouter.post("/submit", validate(submitSchema), async (req, res, next) => {
  try {
    const setting = await getSetting();
    if (!setting?.enabled) {
      return res.status(404).json({ error: "Der Kummerkasten ist derzeit nicht aktiv." });
    }
    if (!setting.access_password_hash) {
      return res.status(403).json({ error: "Für den Kummerkasten ist noch kein Kennwort gesetzt." });
    }
    if (!verifyPublicToken(req.body.public_access_token)) {
      return res.status(401).json({ error: "Zugriff abgelaufen. Bitte Kennwort erneut eingeben." });
    }

    const payload = {
      title: req.body.title.trim(),
      description: req.body.description.trim(),
      submitter_name: normalizeText(req.body.submitter_name),
      submitter_contact: normalizeText(req.body.submitter_contact),
    };

    const agendaTitle = shorten(`Kummerkasten: ${payload.title}`, 255);
    const agendaDescription = buildAgendaDescription({
      description: payload.description,
      submitterName: payload.submitter_name,
      submitterContact: payload.submitter_contact,
      targetCommitteeName: setting.target_committee_name,
    });

    const client = await pool.connect();
    let result;
    try {
      await client.query("BEGIN");
      const agendaResult = await client.query(
        `INSERT INTO meeting_agenda_item(meeting_id, agenda_number, title, description)
         VALUES (NULL, NULL, $1, $2)
         RETURNING id, title, description, created_at`,
        [agendaTitle, agendaDescription]
      );

      const agendaItem = agendaResult.rows[0];
      const submissionResult = await client.query(
        `INSERT INTO kummerkasten_submission(
           agenda_item_id,
           title,
           description,
           submitter_name,
           submitter_contact,
           target_committee_id
         )
         VALUES ($1, $2, $3, $4, $5, $6)
         RETURNING id, created_at`,
        [
          agendaItem.id,
          payload.title,
          payload.description,
          payload.submitter_name,
          payload.submitter_contact,
          setting.target_committee_id || null,
        ]
      );

      await client.query("COMMIT");
      result = { agendaItem, submission: submissionResult.rows[0] };
    } catch (err) {
      await client.query("ROLLBACK");
      throw err;
    } finally {
      client.release();
    }

    await writeAudit({
      actorId: null,
      action: "KUMMERKASTEN_SUBMITTED",
      entityType: "agenda_item",
      entityId: result.agendaItem.id,
      metadata: { submission_id: result.submission.id },
      ipAddress: req.ip,
    });

    res.status(201).json({
      ok: true,
      submission_id: result.submission.id,
      agenda_item_id: result.agendaItem.id,
      title: result.agendaItem.title,
    });
  } catch (err) {
    next(err);
  }
});

adminRouter.use(authenticate);

adminRouter.get(
  "/settings",
  requirePermission("kummerkasten.configure", ["admin"]),
  async (req, res, next) => {
    try {
      const data = await withDbSession(req.user, async (db) => {
        const setting = await getSetting(db);
        const committeeResult = await db.query(
          `SELECT id, name
             FROM committee
            ORDER BY name ASC`
        );
        const submissionsResult = await db.query(
          `SELECT s.id,
                  s.agenda_item_id,
                  s.title,
                  s.submitter_name,
                  s.submitter_contact,
                  s.status,
                  s.created_at,
                  ai.title AS agenda_title,
                  c.name AS target_committee_name
             FROM kummerkasten_submission s
             LEFT JOIN meeting_agenda_item ai ON ai.id = s.agenda_item_id
             LEFT JOIN committee c ON c.id = s.target_committee_id
            ORDER BY s.created_at DESC
            LIMIT 25`
        );

        return {
          settings: adminStatus(setting),
          committees: committeeResult.rows,
          submissions: submissionsResult.rows,
          public_path: "/kummerkasten",
        };
      });

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

adminRouter.put(
  "/settings",
  requireMfa,
  requirePermission("kummerkasten.configure", ["admin"]),
  validate(settingsSchema),
  async (req, res, next) => {
    try {
      const passwordInput = typeof req.body.access_password === "string" ? req.body.access_password.trim() : "";
      const payload = {
        enabled: req.body.enabled,
        intro_text: normalizeText(req.body.intro_text) || "Reichen Sie hier vertraulich einen Vorschlag oder ein Anliegen für die Tagesordnung ein.",
        target_committee_id: req.body.target_committee_id || null,
        access_password: passwordInput,
        clear_password: Boolean(req.body.clear_password),
      };

      if (payload.access_password && payload.access_password.length < 4) {
        throw Object.assign(new Error("Kennwort zu kurz"), {
          status: 400,
          publicMessage: "Das globale Kennwort muss mindestens 4 Zeichen lang sein.",
        });
      }

      const data = await withDbSession(req.user, async (db) => {
        const current = await getSetting(db);
        let passwordHash = current?.access_password_hash || null;

        // Ein neu eingegebenes Kennwort gewinnt immer vor "Kennwort entfernen".
        // Dadurch kann ein Admin in einem Schritt "einschalten + Kennwort setzen" speichern,
        // auch wenn die Checkbox versehentlich noch aktiv ist.
        if (payload.clear_password && !payload.access_password) {
          passwordHash = null;
        }
        if (payload.access_password) {
          passwordHash = await argon2.hash(payload.access_password);
        }

        const nextEnabled = payload.enabled ?? Boolean(current?.enabled);
        if (nextEnabled && !passwordHash) {
          throw Object.assign(new Error("Kennwort erforderlich"), {
            status: 400,
            publicMessage: "Zum Einschalten des Kummerkastens muss zuerst ein Kennwort eingegeben und gespeichert werden.",
          });
        }

        await db.query(
          `INSERT INTO kummerkasten_setting(
             id,
             enabled,
             access_password_hash,
             intro_text,
             target_committee_id,
             created_by,
             updated_by
           )
           VALUES (1, $1, $2, $3, $4, $5, $5)
           ON CONFLICT (id) DO UPDATE
             SET enabled = EXCLUDED.enabled,
                 access_password_hash = EXCLUDED.access_password_hash,
                 intro_text = EXCLUDED.intro_text,
                 target_committee_id = EXCLUDED.target_committee_id,
                 updated_by = EXCLUDED.updated_by`,
          [
            nextEnabled,
            passwordHash,
            payload.intro_text,
            payload.target_committee_id,
            req.user.sub,
          ]
        );

        const setting = await getSetting(db);
        return { settings: adminStatus(setting) };
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "KUMMERKASTEN_SETTINGS_UPDATED",
        entityType: "kummerkasten_setting",
        entityId: 1,
        metadata: { enabled: data.settings.enabled, password_set: data.settings.password_set },
        ipAddress: req.ip,
      });

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

export default adminRouter;
