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

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

const writeGuard = [requireMfa, requirePermission("templates.write", ["admin", "chair", "secretary"])];

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

const templateSchema = z.object({
  title: z.string().min(3).max(255),
  category: z.string().min(2).max(50),
  description: z.string().max(4000).optional().nullable(),
  content: z.string().min(3).max(200000),
  content_format: z.enum(["markdown", "html"]).default("markdown"),
  logo_asset_id: nullablePositiveInt,
  version_label: z.string().min(1).max(30).default("v1"),
  is_active: z.boolean().default(true),
});

const updateTemplateSchema = templateSchema.partial();

const assetUpdateSchema = z.object({
  title: z.string().min(1).max(255).optional(),
  description: z.string().max(2000).optional().nullable(),
  is_active: z.boolean().optional(),
});

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

function normalizeTemplatePayload(payload, userId, isCreate = false) {
  const normalized = { ...payload };
  if (normalized.description !== undefined) normalized.description = normalizeNullableText(normalized.description);
  if (normalized.logo_asset_id === undefined) normalized.logo_asset_id = null;
  if (!normalized.content_format) normalized.content_format = "markdown";
  if (isCreate) {
    normalized.created_by = userId;
  }
  normalized.updated_by = userId;
  return normalized;
}

function mapAsset(row) {
  if (!row) return null;
  return {
    id: row.id,
    title: row.title,
    description: row.description,
    file_name: row.file_name,
    mime_type: row.mime_type,
    size_bytes: row.size_bytes,
    is_active: row.is_active,
    created_at: row.created_at,
    uploaded_by_name: row.uploaded_by_name,
  };
}

async function assertAssetExists(db, assetId) {
  if (!assetId) return null;
  const result = await db.query(
    `SELECT id FROM template_asset WHERE id = $1::bigint AND is_active = TRUE`,
    [assetId]
  );
  if (!result.rows[0]) {
    throw Object.assign(new Error("Logo/Bild wurde nicht gefunden"), {
      status: 400,
      publicMessage: "Das ausgewählte Logo/Bild wurde nicht gefunden oder ist deaktiviert.",
    });
  }
  return result.rows[0];
}

async function streamTemplateAsset(row, res, disposition = "inline") {
  const { stream, contentType } = await getObjectStream(row.storage_path);
  const safeFilename = encodeURIComponent(row.file_name || row.title || `template-asset-${row.id}`);
  res.setHeader("Content-Type", contentType || row.mime_type || "application/octet-stream");
  res.setHeader("Content-Disposition", `${disposition}; filename="${safeFilename}"`);
  res.setHeader("Cache-Control", "private, max-age=300");
  stream.pipe(res);
}

router.get("/placeholders", async (req, res) => {
  res.json([
    { token: "{{template_logo}}", label: "Template-Logo/Bild" },
    { token: "{{committee_name}}", label: "Gremium / Betriebsrat" },
    { token: "{{meeting_number}}", label: "Sitzungsnummer" },
    { token: "{{meeting_date}}", label: "Sitzungsdatum" },
    { token: "{{meeting_title_block}}", label: "Sitzungstitel" },
    { token: "{{meeting_type}}", label: "Sitzungsart" },
    { token: "{{start_time}}", label: "Beginn" },
    { token: "{{end_time}}", label: "Ende" },
    { token: "{{location}}", label: "Ort" },
    { token: "{{chair_name}}", label: "Sitzungsleitung" },
    { token: "{{secretary_name}}", label: "Schriftführung" },
    { token: "{{quorum_statement}}", label: "Beschlussfähigkeit" },
    { token: "{{attendees_block}}", label: "Anwesende Mitglieder" },
    { token: "{{protocol_notes}}", label: "Protokollnotizen" },
    { token: "{{agenda_sections}}", label: "TOPs / Beratung / Beschlüsse" },
    { token: "{{signature_place}}", label: "Ort/Datum Unterschrift" },
  ]);
});

router.get("/assets", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `SELECT a.id,
                a.title,
                a.description,
                a.file_name,
                a.mime_type,
                a.size_bytes,
                a.is_active,
                a.created_at,
                u.full_name AS uploaded_by_name
           FROM template_asset a
           LEFT JOIN app_user u ON u.id = a.uploaded_by
          WHERE a.is_active = TRUE
          ORDER BY a.created_at DESC, a.id DESC`
      );
      return result.rows.map(mapAsset);
    });
    res.json(rows);
  } catch (err) {
    next(err);
  }
});

router.post(
  "/assets/upload",
  ...writeGuard,
  upload.single("file"),
  async (req, res, next) => {
    try {
      if (!req.file) return res.status(400).json({ error: "Datei fehlt" });

      const detected = await fileTypeFromBuffer(req.file.buffer);
      const detectedMime = detected?.mime || req.file.mimetype || "application/octet-stream";
      if (!["image/png", "image/jpeg"].includes(detectedMime)) {
        return res.status(415).json({ error: "Nur PNG- und JPEG-Bilder sind als Template-Logo/Bild erlaubt." });
      }

      const scan = await scanBuffer(req.file.buffer);
      if (!scan.clean) {
        return res.status(422).json({ error: "Datei durch Virenscan abgelehnt", scan_result: scan.raw });
      }

      const sha256 = crypto.createHash("sha256").update(req.file.buffer).digest("hex");
      const safeName = req.file.originalname.replace(/[^a-zA-Z0-9._-]/g, "_");
      const storageKey = `${new Date().toISOString().slice(0, 10)}/template-assets/${uuidv4()}-${safeName}`;
      const stored = await saveObject(storageKey, req.file.buffer, detectedMime);

      const asset = await withDbSession(req.user, async (db) => {
        const result = await db.query(
          `INSERT INTO template_asset(
             title, description, file_name, storage_path, storage_provider, mime_type, size_bytes, sha256_hash, uploaded_by, is_active
           ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,TRUE)
           RETURNING *`,
          [
            normalizeNullableText(req.body.title) || req.file.originalname,
            normalizeNullableText(req.body.description),
            req.file.originalname,
            stored.storage_path,
            stored.storage_provider,
            detectedMime,
            req.file.size,
            sha256,
            req.user.sub,
          ]
        );
        return result.rows[0];
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "TEMPLATE_ASSET_UPLOADED",
        entityType: "template_asset",
        entityId: asset.id,
        ipAddress: req.ip,
        metadata: { file_name: req.file.originalname, mime_type: detectedMime, sha256 },
      });

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

router.get("/assets/:id/view", async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `SELECT id, title, file_name, storage_path, mime_type
           FROM template_asset
          WHERE id = $1::bigint
            AND is_active = TRUE`,
        [req.params.id]
      );
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Bild nicht gefunden" });
    await streamTemplateAsset(row, res, "inline");
  } catch (err) {
    next(err);
  }
});

router.put("/assets/:id", ...writeGuard, validate(assetUpdateSchema), async (req, res, next) => {
  try {
    const payload = {
      title: normalizeNullableText(req.body.title),
      description: normalizeNullableText(req.body.description),
      is_active: req.body.is_active,
    };
    const fields = Object.entries(payload).filter(([, value]) => value !== undefined);
    if (!fields.length) return res.status(400).json({ error: "Keine Felder zum Aktualisieren" });

    const sets = fields.map(([key], index) => `${key} = $${index + 1}`);
    const values = fields.map(([, value]) => value);
    values.push(req.params.id);

    const row = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `UPDATE template_asset SET ${sets.join(", ")} WHERE id = $${values.length}::bigint RETURNING *`,
        values
      );
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Bild nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "TEMPLATE_ASSET_UPDATED", entityType: "template_asset", entityId: row.id, ipAddress: req.ip, metadata: req.body });
    res.json(mapAsset(row));
  } catch (err) {
    next(err);
  }
});

router.delete("/assets/:id", ...writeGuard, async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      await db.query(`UPDATE template_item SET logo_asset_id = NULL WHERE logo_asset_id = $1::bigint`, [req.params.id]);
      const result = await db.query(
        `UPDATE template_asset SET is_active = FALSE WHERE id = $1::bigint RETURNING *`,
        [req.params.id]
      );
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Bild nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "TEMPLATE_ASSET_DEACTIVATED", entityType: "template_asset", entityId: row.id, ipAddress: req.ip });
    res.json({ ok: true, row: mapAsset(row) });
  } catch (err) {
    next(err);
  }
});

router.get("/", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `SELECT t.id,
                t.title,
                t.category,
                t.description,
                t.content_format,
                t.version_label,
                t.is_active,
                t.logo_asset_id,
                a.title AS logo_title,
                t.updated_at,
                t.created_at
           FROM template_item t
           LEFT JOIN template_asset a ON a.id = t.logo_asset_id
          ORDER BY t.category, t.title, t.id`
      );
      return result.rows;
    });
    res.json(rows);
  } catch (err) {
    next(err);
  }
});

router.get("/:id", async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const result = await db.query(
        `SELECT t.*,
                a.title AS logo_title,
                a.file_name AS logo_file_name,
                a.mime_type AS logo_mime_type,
                a.size_bytes AS logo_size_bytes
           FROM template_item t
           LEFT JOIN template_asset a ON a.id = t.logo_asset_id
          WHERE t.id = $1::bigint`,
        [req.params.id]
      );
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Nicht gefunden" });
    res.json(row);
  } catch (err) {
    next(err);
  }
});

router.post("/", ...writeGuard, validate(templateSchema), async (req, res, next) => {
  try {
    const payload = normalizeTemplatePayload(req.body, req.user.sub, true);
    const row = await withDbSession(req.user, async (db) => {
      await assertAssetExists(db, payload.logo_asset_id);
      const result = await db.query(
        `INSERT INTO template_item(
           title, category, description, content, content_format, logo_asset_id, version_label, is_active, created_by, updated_by
         ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
         RETURNING *`,
        [
          payload.title,
          payload.category,
          payload.description,
          payload.content,
          payload.content_format,
          payload.logo_asset_id,
          payload.version_label,
          payload.is_active,
          payload.created_by,
          payload.updated_by,
        ]
      );
      return result.rows[0];
    });
    await writeAudit({ actorId: req.user.sub, action: "TEMPLATE_CREATED", entityType: "template", entityId: row.id, ipAddress: req.ip, metadata: { title: row.title, category: row.category } });
    res.status(201).json(row);
  } catch (err) {
    next(err);
  }
});

router.put("/:id", ...writeGuard, validate(updateTemplateSchema), async (req, res, next) => {
  try {
    const payload = normalizeTemplatePayload(req.body, req.user.sub, false);
    const fields = ["title", "category", "description", "content", "content_format", "logo_asset_id", "version_label", "is_active", "updated_by"].filter(
      (field) => payload[field] !== undefined
    );
    if (!fields.length) return res.status(400).json({ error: "Keine Felder zum Aktualisieren" });

    const row = await withDbSession(req.user, async (db) => {
      if (payload.logo_asset_id !== undefined) await assertAssetExists(db, payload.logo_asset_id);
      const sets = fields.map((field, index) => `${field} = $${index + 1}`);
      const values = fields.map((field) => payload[field]);
      values.push(req.params.id);
      const result = await db.query(
        `UPDATE template_item SET ${sets.join(", ")}, updated_at = NOW() WHERE id = $${values.length}::bigint RETURNING *`,
        values
      );
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "TEMPLATE_UPDATED", entityType: "template", entityId: row.id, ipAddress: req.ip, metadata: req.body });
    res.json(row);
  } catch (err) {
    next(err);
  }
});

router.delete("/:id", ...writeGuard, async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, async (db) => {
      const result = await db.query(`DELETE FROM template_item WHERE id = $1::bigint RETURNING *`, [req.params.id]);
      return result.rows[0];
    });
    if (!row) return res.status(404).json({ error: "Nicht gefunden" });
    await writeAudit({ actorId: req.user.sub, action: "TEMPLATE_DELETED", entityType: "template", entityId: row.id, ipAddress: req.ip });
    res.json({ ok: true, row });
  } catch (err) {
    next(err);
  }
});

export default router;
