import { NextRequest, NextResponse } from "next/server";
import { hasPermission } from "@/lib/adminAuth";
import { connectDB } from "@/lib/db";
import { Order } from "@/models/Order";
import { Invoice } from "@/models/Invoice";
import { Expense } from "@/models/Expense";
import { Income } from "@/models/Income";
import { EXPENSE_CATEGORY_LABELS, type ExpenseCategory } from "@/lib/expenseConstants";
import { formatDateDE, formatDateTimeDE } from "@/lib/orderConstants";

// EÜR = Einnahmen-Überschuss-Rechnung, arbeitet nach dem Zufluss-/Abfluss-
// Prinzip (Ist-Besteuerung): Einnahmen zählen, wenn das Geld einging (nicht
// wenn der Preis vereinbart oder die Rechnung gestellt wurde), Ausgaben
// zählen an ihrem erfassten Datum. Bewusst nur ein sauber aufbereiteter
// CSV-Export für den Steuerberater/ELSTER — keine eigene Steuererklärung.
function csvEscape(value: string | number): string {
  const s = String(value);
  return /[",\n;]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
}

export async function GET(req: NextRequest) {
  if (!(await hasPermission("statistics"))) return new NextResponse("Unauthorized", { status: 401 });

  const yearParam = req.nextUrl.searchParams.get("year");
  const year = yearParam ? parseInt(yearParam, 10) : new Date().getFullYear();
  if (!Number.isFinite(year)) return new NextResponse("Invalid year", { status: 400 });

  const rangeStart = new Date(Date.UTC(year, 0, 1));
  const rangeEnd = new Date(Date.UTC(year + 1, 0, 1));

  await connectDB();
  const [paidOrders, expenses, manualIncome, refundedOrders] = await Promise.all([
    Order.find({ paid: true, paidAt: { $gte: rangeStart, $lt: rangeEnd } }).lean(),
    Expense.find({ date: { $gte: rangeStart, $lt: rangeEnd } }).sort({ date: 1 }).lean(),
    Income.find({ date: { $gte: rangeStart, $lt: rangeEnd } }).sort({ date: 1 }).lean(),
    Order.find({ refundedAmount: { $gt: 0 }, refundedAt: { $gte: rangeStart, $lt: rangeEnd } })
      .select("characterOrConcept refundedAmount refundedAt refundNote")
      .lean(),
  ]);
  const invoicesByOrderId = new Map(
    (await Invoice.find({ orderId: { $in: paidOrders.map((o) => o._id) } }).lean()).map((inv) => [String(inv.orderId), inv])
  );

  // Als bezahlt markieren setzt keine Rechnung voraus (z. B. Bar-/TFP-Fälle
  // oder schlicht vergessen) — ein als bezahlt markierter Auftrag OHNE
  // Rechnung wird hier bewusst NICHT stillschweigend mit 0 € durchgewunken
  // (das würde die Betriebseinnahmen unbemerkt zu niedrig ausweisen),
  // sondern als auffällige Warnzeile mit eigenem Betrag von 0 € aufgeführt,
  // damit Rakku die fehlende Rechnung gezielt nachtragen kann.
  const ordersMissingInvoice: string[] = [];
  const orderIncomeRows = paidOrders.map((o) => {
    const invoice = invoicesByOrderId.get(String(o._id));
    if (!invoice) ordersMissingInvoice.push(o.characterOrConcept);
    return {
      date: o.paidAt as Date,
      description: invoice
        ? `${o.characterOrConcept} (Rechnung ${invoice.number})`
        : `⚠ ${o.characterOrConcept} — KEINE RECHNUNG, BETRAG PRÜFEN`,
      amount: invoice?.totalAmount ?? 0,
    };
  });
  const manualIncomeRows = manualIncome.map((i) => ({
    date: i.date,
    description: `${i.description} (manuell erfasst)`,
    amount: i.amount,
  }));
  // Rückerstattungen mindern die Betriebseinnahmen im Zeitpunkt der
  // Rücküberweisung (Abfluss-Prinzip) — als negative Zeile in derselben
  // Liste statt eigenem Abschnitt, damit sie in totalIncome korrekt gegenrechnen.
  const refundRows = refundedOrders.map((o) => ({
    date: o.refundedAt as Date,
    description: `⤷ Rückerstattung: ${o.characterOrConcept}${o.refundNote ? ` (${o.refundNote})` : ""}`,
    amount: -o.refundedAmount,
  }));
  const incomeRows = [...orderIncomeRows, ...manualIncomeRows, ...refundRows].sort((a, b) => a.date.getTime() - b.date.getTime());

  const totalIncome = incomeRows.reduce((sum, r) => sum + r.amount, 0);
  const totalExpenses = expenses.reduce((sum, e) => sum + e.amount, 0);

  const lines: string[] = [];
  lines.push(`EÜR-Vorbereitung ${year} — Rakku Photography`);
  lines.push(`Erzeugt am;${formatDateTimeDE(new Date())}`);
  lines.push("");
  lines.push(`Betriebseinnahmen gesamt;${totalIncome.toFixed(2)} EUR`);
  lines.push(`Betriebsausgaben gesamt;${totalExpenses.toFixed(2)} EUR`);
  lines.push(`Gewinn / Verlust;${(totalIncome - totalExpenses).toFixed(2)} EUR`);
  if (ordersMissingInvoice.length > 0) {
    lines.push("");
    lines.push(
      `⚠ ACHTUNG;${ordersMissingInvoice.length} als bezahlt markierte(r) Auftrag/Aufträge ohne Rechnung — Betrag fehlt oben in „Betriebseinnahmen gesamt". Rechnung nachtragen und Export neu ziehen: ${ordersMissingInvoice.join(", ")}`
    );
  }
  lines.push("");

  lines.push("BETRIEBSEINNAHMEN");
  lines.push(["Datum", "Beschreibung", "Betrag (EUR)"].map(csvEscape).join(";"));
  for (const r of incomeRows) {
    lines.push([formatDateDE(new Date(r.date)), r.description, r.amount.toFixed(2)].map(csvEscape).join(";"));
  }
  lines.push("");

  lines.push("BETRIEBSAUSGABEN");
  lines.push(["Datum", "Kategorie", "Beschreibung", "Betrag (EUR)", "davon USt. (EUR)"].map(csvEscape).join(";"));
  for (const e of expenses) {
    lines.push(
      [
        formatDateDE(new Date(e.date)),
        EXPENSE_CATEGORY_LABELS[e.category as ExpenseCategory],
        e.description,
        e.amount.toFixed(2),
        (e.vatAmount || 0).toFixed(2),
      ]
        .map(csvEscape)
        .join(";")
    );
  }

  const csv = "﻿" + lines.join("\n"); // BOM für korrekte Umlaute in Excel
  return new NextResponse(csv, {
    headers: {
      "Content-Type": "text/csv; charset=utf-8",
      "Content-Disposition": `attachment; filename="euer-${year}.csv"`,
      "Cache-Control": "private, max-age=0, no-store",
    },
  });
}
