import crypto from "crypto";
import { v4 as uuidv4 } from "uuid";
import { saveObject } from "./storage.js";

const AUTO_DESCRIPTION = "Automatisch beim Beenden der Sitzung erzeugter Beschlussbericht.";

export const DEFAULT_MEETING_DECISION_REPORT_TEMPLATE = `# Beschlussbericht zur BR-Sitzung am {{meeting_date}}
VERTRAULICH

{{meeting_title_block}}

## Sitzungsdaten
Sitzungsnummer: {{meeting_number}}
Sitzungsart: {{meeting_type}}
Sitzungsdatum: {{meeting_date}}
Sitzungsort: {{location}}
Sitzungsleitung: {{chair_name}}
Schriftführung: {{secretary_name}}

## Behandelte Beschlüsse ab TOP 1
{{decision_sections}}

{{signature_place}}
______________________________
{{chair_name}}

______________________________
{{secretary_name}}`;

function escapeRtf(value) {
  return String(value ?? "")
    .replace(/\\/g, "\\\\")
    .replace(/\{/g, "\\{")
    .replace(/\}/g, "\\}")
    .replace(/\t/g, "\\tab ");
}

function formatDate(value) {
  if (!value) return "";
  const date = value instanceof Date ? value : new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return date.toLocaleDateString("de-DE");
}

function formatDateTime(value) {
  if (!value) return "";
  const date = value instanceof Date ? value : new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return date.toLocaleString("de-DE");
}

function meetingTypeLabel(value) {
  if (value === "ausserordentlich") return "Außerordentlich";
  return "Ordentlich";
}

function decisionStatusLabel(value) {
  switch (String(value ?? "").trim().toLowerCase()) {
    case "in_bearbeitung":
      return "In Bearbeitung";
    case "rueckfrage":
      return "Rückfrage";
    case "abgeschlossen":
      return "Abgeschlossen";
    default:
      return value || "offen";
  }
}

function sanitizeLine(value, fallback = "-") {
  const text = String(value ?? "").trim();
  return text || fallback;
}

function computeDecisionOutcome(decision) {
  const yes = Number(decision?.votes_yes ?? 0);
  const no = Number(decision?.votes_no ?? 0);
  if (yes === 0 && no === 0) return "Ergebnis: nicht hinterlegt";
  if (yes > no) return "Ergebnis: angenommen";
  if (no > yes) return "Ergebnis: abgelehnt";
  return "Ergebnis: Stimmengleichheit";
}

function replaceTokens(template, values) {
  return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_, key) => {
    const value = values[key];
    if (value === undefined || value === null) return "";
    return String(value);
  });
}

function linesToRtf(content) {
  const lines = String(content ?? "").replace(/\r\n/g, "\n").split("\n");
  const rtfLines = [
    "{\\rtf1\\ansi\\deff0",
    "{\\fonttbl{\\f0 Arial;}}",
    "\\paperw11907\\paperh16840\\margl1134\\margr1134\\margt1134\\margb1134",
    "\\viewkind4\\uc1",
  ];

  for (const rawLine of lines) {
    const line = rawLine ?? "";
    const trimmed = line.trim();

    if (!trimmed) {
      rtfLines.push("{\\pard\\sa140\\par}");
      continue;
    }

    if (trimmed === "VERTRAULICH") {
      rtfLines.push(`{\\pard\\qc\\sa220\\b\\fs26 ${escapeRtf(trimmed)}\\par}`);
      continue;
    }

    if (trimmed.startsWith("# ")) {
      rtfLines.push(`{\\pard\\qc\\sa220\\b\\fs32 ${escapeRtf(trimmed.slice(2))}\\par}`);
      continue;
    }

    if (trimmed.startsWith("## ")) {
      rtfLines.push(`{\\pard\\sa200\\b\\fs26 ${escapeRtf(trimmed.slice(3))}\\par}`);
      continue;
    }

    if (trimmed.startsWith("### ")) {
      rtfLines.push(`{\\pard\\sa160\\b\\fs23 ${escapeRtf(trimmed.slice(4))}\\par}`);
      continue;
    }

    if (trimmed.startsWith("- ")) {
      rtfLines.push(`{\\pard\\li540\\fi-240\\sa120\\fs22 ${escapeRtf(trimmed)}\\par}`);
      continue;
    }

    rtfLines.push(`{\\pard\\sa120\\fs22 ${escapeRtf(line)}\\par}`);
  }

  rtfLines.push("}");
  return rtfLines.join("\n");
}

function renderDecisionSections(agendaItems = []) {
  const itemsWithDecisions = agendaItems.filter((item) => Array.isArray(item.decisions) && item.decisions.length > 0);
  if (!itemsWithDecisions.length) {
    return "Für diese Sitzung wurden keine Beschlüsse dokumentiert.";
  }

  return itemsWithDecisions
    .map((item) => {
      const lines = [`## TOP ${sanitizeLine(item.agenda_number, String(item.id))}: ${sanitizeLine(item.title, "Ohne Titel")}`];

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

      if (item.description?.trim()) {
        lines.push(item.description.trim());
      }

      item.decisions.forEach((decision) => {
        lines.push(`### Beschluss ${sanitizeLine(decision.decision_number, String(decision.id))}: ${sanitizeLine(decision.title, "Ohne Titel")}`);
        lines.push(decision.decision_text?.trim() || "Kein Beschlusstext hinterlegt.");
        lines.push([
          `Ja-Stimmen: ${Number(decision.votes_yes ?? 0)}`,
          `Nein-Stimmen: ${Number(decision.votes_no ?? 0)}`,
          `Enthaltungen: ${Number(decision.votes_abstain ?? 0)}`,
        ].join(" | "));
        lines.push(computeDecisionOutcome(decision));
        lines.push(`Status: ${decisionStatusLabel(decision.status)}`);

        if (decision.responsible_name) {
          lines.push(`Verantwortlich: ${decision.responsible_name}`);
        }

        if (decision.due_date) {
          lines.push(`Frist: ${formatDateTime(decision.due_date)}`);
        }
      });

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

function buildDecisionReportValues(meeting, agendaItems) {
  const title = meeting.title?.trim();
  return {
    meeting_number: sanitizeLine(meeting.meeting_number, "offen"),
    meeting_title_block: title ? `Titel: ${title}` : "",
    meeting_date: formatDate(meeting.meeting_date),
    meeting_type: meetingTypeLabel(meeting.meeting_type),
    location: sanitizeLine(meeting.location, "nicht hinterlegt"),
    chair_name: sanitizeLine(meeting.chair_name, "nicht hinterlegt"),
    secretary_name: sanitizeLine(meeting.secretary_name, "nicht hinterlegt"),
    decision_sections: renderDecisionSections(agendaItems),
    signature_place: `${sanitizeLine(meeting.location, "Ort")}, den ${formatDate(meeting.meeting_date)}`,
  };
}

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

async function fetchActiveTemplate(db) {
  const result = await db.query(
    `SELECT id, title, content, version_label
       FROM template_item
      WHERE category = 'meeting_decision_report'
        AND is_active = TRUE
      ORDER BY updated_at DESC, id DESC
      LIMIT 1`
  );
  return result.rows[0] || null;
}

async function fetchMeetingDecisionReportData(db, meetingId) {
  const meetingResult = await db.query(
    `SELECT m.id,
            m.meeting_number,
            m.title,
            m.meeting_type,
            m.meeting_date,
            m.location,
            chair.full_name AS chair_name,
            secretary.full_name AS secretary_name
       FROM meeting m
       LEFT JOIN app_user chair ON chair.id = m.chair_user_id
       LEFT JOIN app_user secretary ON secretary.id = m.secretary_user_id
      WHERE m.id = $1`,
    [meetingId]
  );

  const meeting = meetingResult.rows[0];
  if (!meeting) return null;

  const agendaResult = await db.query(
    `SELECT ai.id,
            ai.agenda_number,
            ai.title,
            ai.description,
            ai.case_id,
            c.case_number
       FROM meeting_agenda_item ai
       LEFT JOIN br_case c ON c.id = ai.case_id
      WHERE ai.meeting_id = $1
      ORDER BY
        CASE WHEN ai.agenda_number ~ '^[0-9]+$' THEN ai.agenda_number::int ELSE NULL END NULLS LAST,
        ai.agenda_number NULLS LAST,
        ai.id`,
    [meetingId]
  );

  const agendaItems = agendaResult.rows;
  const agendaIds = agendaItems.map((item) => item.id);
  const decisionMap = new Map(agendaIds.map((id) => [id, []]));

  if (agendaIds.length) {
    const decisionResult = await db.query(
      `SELECT d.id,
              d.agenda_item_id,
              d.decision_number,
              d.title,
              d.decision_text,
              d.status,
              d.votes_yes,
              d.votes_no,
              d.votes_abstain,
              d.due_date,
              u.full_name AS responsible_name
         FROM decision d
         LEFT JOIN app_user u ON u.id = d.responsible_user_id
        WHERE d.meeting_id = $1
          AND d.agenda_item_id = ANY($2::bigint[])
        ORDER BY d.created_at, d.id`,
      [meetingId, agendaIds]
    );

    decisionResult.rows.forEach((row) => {
      const bucket = decisionMap.get(row.agenda_item_id) || [];
      bucket.push(row);
      decisionMap.set(row.agenda_item_id, bucket);
    });
  }

  return {
    meeting,
    agendaItems: agendaItems.map((item) => ({ ...item, decisions: decisionMap.get(item.id) || [] })),
  };
}

export async function ensureMeetingDecisionReportDocument({ db, meetingId, actorId }) {
  const existing = await db.query(
    `SELECT id, document_number, title, file_name, uploaded_at
       FROM document_item
      WHERE meeting_id = $1
        AND document_type = 'bericht'
        AND description = $2
        AND deleted_at IS NULL
      ORDER BY uploaded_at DESC, id DESC
      LIMIT 1`,
    [meetingId, AUTO_DESCRIPTION]
  );

  if (existing.rows[0]) {
    return existing.rows[0];
  }

  const data = await fetchMeetingDecisionReportData(db, meetingId);
  if (!data) return null;

  const template = await fetchActiveTemplate(db);
  const templateContent = template?.content?.trim() || DEFAULT_MEETING_DECISION_REPORT_TEMPLATE;
  const values = buildDecisionReportValues(data.meeting, data.agendaItems);
  const renderedText = replaceTokens(templateContent, values);
  const rtf = linesToRtf(renderedText);
  const buffer = Buffer.from(rtf, "utf8");

  const filenameBase = `${data.meeting.meeting_number || `sitzung-${meetingId}`}-beschlussbericht`;
  const safeFilename = filenameBase.replace(/[^a-zA-Z0-9._-]/g, "_");
  const storageKey = `${new Date().toISOString().slice(0, 10)}/${uuidv4()}-${safeFilename}.rtf`;
  const sha256 = crypto.createHash("sha256").update(buffer).digest("hex");
  const docMeta = await saveObject(storageKey, buffer, "application/rtf");

  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,
       updated_at
     ) VALUES (
       $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,NULL,$13,NULL,$14,$15,$16,$17,NOW(),NOW()
     )
     RETURNING id, document_number, title, file_name, uploaded_at`,
    [
      generateDocumentNumber(),
      `Beschlussbericht ${data.meeting.meeting_number}`,
      "bericht",
      AUTO_DESCRIPTION,
      template?.version_label || "v1",
      `${safeFilename}.rtf`,
      docMeta.storage_path,
      docMeta.storage_provider,
      "application/rtf",
      buffer.length,
      sha256,
      "vertraulich",
      meetingId,
      actorId,
      "clean",
      "system",
      `Automatisch generiert aus Vorlage ${template?.title || "meeting_decision_report"}`,
    ]
  );

  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, actorId, "clean", "system", "Automatisch generierter Beschlussbericht"]
  );

  return insert.rows[0];
}
