import { ProcessedReceipt, TaxBreakdownItem } from "@/lib/schema/receipt";

/**
 * Gemeinsame Formatierungs- und Aggregations-Helfer für die Dashboard-Ansichten.
 *
 * Alle Funktionen sind bewusst defensiv: Datensätze, die vor der Einführung
 * neuer Felder in IndexedDB gelandet sind, dürfen weder crashen noch als
 * "0,00 €" durchgereicht werden.
 */

/** Fällt auf EUR zurück, wenn der Beleg keine Währung mitbringt. */
export function receiptCurrency(
  receipt: Partial<ProcessedReceipt> | null | undefined
): string {
  const raw = receipt?.currency;
  if (typeof raw === "string" && raw.trim().length >= 3) {
    return raw.trim().toUpperCase();
  }
  return "EUR";
}

const currencyFormatters = new Map<string, Intl.NumberFormat>();

function formatterFor(currency: string): Intl.NumberFormat {
  const key = currency.toUpperCase();
  let formatter = currencyFormatters.get(key);
  if (!formatter) {
    try {
      formatter = new Intl.NumberFormat("de-DE", {
        style: "currency",
        currency: key,
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
      });
    } catch {
      // Unbekannter ISO-Code -> neutrale Zahlenformatierung mit Suffix.
      formatter = new Intl.NumberFormat("de-DE", {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
      });
    }
    currencyFormatters.set(key, formatter);
  }
  return formatter;
}

/**
 * Formatiert einen Betrag in der Währung des Belegs (nicht hart in Euro).
 *
 * @example formatMoney(12.5, "CHF") // "12,50 CHF"
 */
export function formatMoney(
  value: number | null | undefined,
  currency: string = "EUR"
): string {
  const amount = typeof value === "number" && Number.isFinite(value) ? value : 0;
  const formatter = formatterFor(currency);
  const output = formatter.format(amount);
  // Fallback-Formatter (unbekannte Währung) kennt kein Symbol -> Code anhängen.
  return output.includes(currency) || /[^\d\s.,-]/.test(output)
    ? output
    : `${output} ${currency}`;
}

/** Formatiert einen Betrag ohne Währungssymbol (für Eingabefelder). */
export function formatAmountInput(value: number | null | undefined): string {
  if (typeof value !== "number" || !Number.isFinite(value)) return "";
  return value.toFixed(2);
}

/**
 * Parst eine deutsche oder englische Betragseingabe.
 * `"1.234,56"`, `"1234,56"` und `"1234.56"` ergeben alle `1234.56`.
 *
 * @returns Die Zahl oder `null`, wenn die Eingabe kein Betrag ist.
 */
export function parseAmountInput(raw: string): number | null {
  const trimmed = (raw ?? "").trim();
  if (trimmed === "") return null;
  let normalized = trimmed.replace(/\s|€|EUR/gi, "");
  const lastComma = normalized.lastIndexOf(",");
  const lastDot = normalized.lastIndexOf(".");
  if (lastComma > -1 && lastDot > -1) {
    // Beide vorhanden: das letzte Zeichen ist das Dezimaltrennzeichen.
    if (lastComma > lastDot) {
      normalized = normalized.replace(/\./g, "").replace(",", ".");
    } else {
      normalized = normalized.replace(/,/g, "");
    }
  } else if (lastComma > -1) {
    normalized = normalized.replace(",", ".");
  }
  const parsed = Number.parseFloat(normalized);
  return Number.isFinite(parsed) ? parsed : null;
}

/** Steuerbetrag für einen bestimmten Satz, oder `null`, wenn nicht ausgewiesen. */
export function taxAmountForRate(
  receipt: Partial<ProcessedReceipt> | null | undefined,
  rate: number
): number | null {
  const entry = (receipt?.taxBreakdown ?? []).find(
    (item: TaxBreakdownItem) => (item?.ratePercent ?? -1) === rate
  );
  if (!entry) return null;
  return typeof entry.taxAmount === "number" ? entry.taxAmount : null;
}

/** Summe aller ausgewiesenen Steuerbeträge eines Belegs. */
export function totalTaxOf(
  receipt: Partial<ProcessedReceipt> | null | undefined
): number {
  return (receipt?.taxBreakdown ?? []).reduce(
    (acc, item) => acc + (item?.taxAmount ?? 0),
    0
  );
}

/** Nettobetrag: gespeicherter Wert, sonst Brutto minus Steuersumme. */
export function netOf(
  receipt: Partial<ProcessedReceipt> | null | undefined
): number {
  if (typeof receipt?.netAmount === "number" && Number.isFinite(receipt.netAmount)) {
    return receipt.netAmount;
  }
  return (receipt?.totalAmount?.value ?? 0) - totalTaxOf(receipt);
}

/** Bruttobetrag, defensiv. */
export function grossOf(
  receipt: Partial<ProcessedReceipt> | null | undefined
): number {
  const value = receipt?.totalAmount?.value;
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
}

/**
 * Alle Steuersätze, die in der übergebenen Belegmenge tatsächlich vorkommen –
 * aufsteigend sortiert. Ersetzt die früher fest verdrahteten Spalten 7 % / 19 %,
 * damit auch 0 %, österreichische 10/20 % oder ausländische Sätze sichtbar sind.
 *
 * Enthält kein Beleg eine Aufschlüsselung, wird `[]` geliefert; die Tabelle
 * blendet die Steuerspalten dann aus.
 */
export function collectTaxRates(
  receipts: Array<Partial<ProcessedReceipt>>
): number[] {
  const rates = new Set<number>();
  for (const receipt of receipts) {
    for (const item of receipt?.taxBreakdown ?? []) {
      const rate = item?.ratePercent;
      if (typeof rate === "number" && Number.isFinite(rate)) {
        rates.add(rate);
      }
    }
  }
  return Array.from(rates).sort((a, b) => a - b);
}

/** Beschriftung einer Steuerspalte, z.B. `TAX 19%` oder `MWST 19%`. */
export function taxRateLabel(rate: number, isDe: boolean = false): string {
  const formatted = Number.isInteger(rate) ? String(rate) : rate.toFixed(1);
  return `${isDe ? "MWST" : "TAX"} ${formatted}%`;
}

/** Gängige DACH-Sätze plus 0 % — für Dropdowns und Schnellwahl. */
export const COMMON_TAX_RATES: readonly number[] = [0, 2.6, 3.8, 7, 8.1, 10, 13, 19, 20];

/** Steuersatz aus einem Eingabefeld, 0–100, eine Nachkommastelle. */
export function parseTaxRatePercent(raw: string): number | null {
  const trimmed = raw.trim().replace("%", "").replace(",", ".");
  if (trimmed === "") return null;
  const parsed = Number.parseFloat(trimmed);
  if (!Number.isFinite(parsed)) return null;
  return Math.max(0, Math.min(100, Math.round(parsed * 10) / 10));
}

export function formatTaxRateInput(rate: number | null | undefined): string {
  if (rate === null || rate === undefined || !Number.isFinite(rate)) return "";
  return Number.isInteger(rate) ? String(rate) : String(rate);
}

/** Summe der Steuerbeträge je Satz über eine Belegmenge. */
export function taxTotalsByRate(
  receipts: Array<Partial<ProcessedReceipt>>
): Array<{ rate: number; amount: number }> {
  const sums = new Map<number, number>();
  for (const receipt of receipts) {
    for (const item of receipt?.taxBreakdown ?? []) {
      const rate = item?.ratePercent;
      if (typeof rate !== "number" || !Number.isFinite(rate)) continue;
      sums.set(rate, (sums.get(rate) ?? 0) + (item?.taxAmount ?? 0));
    }
  }
  return Array.from(sums.entries())
    .map(([rate, amount]) => ({ rate, amount }))
    .sort((a, b) => a.rate - b.rate);
}

/* ------------------------------------------------------------------ */
/* Perioden (Monat / Quartal / Jahr)                                   */
/* ------------------------------------------------------------------ */

export type PeriodMode = "month" | "quarter" | "year" | "all";

export interface Period {
  mode: PeriodMode;
  /** Stabiler Schlüssel: `2026-08`, `2026-Q3`, `2026` oder `all`. */
  key: string;
  /** Lokalisierte Beschriftung, z.B. `August 2026`. */
  label: string;
  /** Inklusive Untergrenze als ISO-Datum, `null` bei `all`. */
  start: string | null;
  /** Inklusive Obergrenze als ISO-Datum, `null` bei `all`. */
  end: string | null;
}

const MONTH_NAMES_DE = [
  "Januar",
  "Februar",
  "März",
  "April",
  "Mai",
  "Juni",
  "Juli",
  "August",
  "September",
  "Oktober",
  "November",
  "Dezember",
];

const MONTH_NAMES_EN = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December",
];

export const MONTH_NAMES = MONTH_NAMES_DE;

function pad(value: number): string {
  return String(value).padStart(2, "0");
}

function lastDayOfMonth(year: number, monthIndex: number): number {
  return new Date(Date.UTC(year, monthIndex + 1, 0)).getUTCDate();
}

/** Baut eine Periode aus Modus, Jahr und (je nach Modus) Monat/Quartal. */
export function buildPeriod(
  mode: PeriodMode,
  year: number,
  unitOrIsDe: number | boolean = 1,
  isDe: boolean = false
): Period {
  const unit = typeof unitOrIsDe === "number" ? unitOrIsDe : 1;
  const de = typeof unitOrIsDe === "boolean" ? unitOrIsDe : isDe;
  if (mode === "all") {
    return { mode, key: "all", label: de ? "Gesamter Zeitraum" : "All Time", start: null, end: null };
  }
  if (mode === "year") {
    return {
      mode,
      key: String(year),
      label: de ? `Jahr ${year}` : `Year ${year}`,
      start: `${year}-01-01`,
      end: `${year}-12-31`,
    };
  }
  if (mode === "quarter") {
    const quarter = Math.min(4, Math.max(1, unit));
    const startMonth = (quarter - 1) * 3;
    const endMonth = startMonth + 2;
    return {
      mode,
      key: `${year}-Q${quarter}`,
      label: `Q${quarter} ${year}`,
      start: `${year}-${pad(startMonth + 1)}-01`,
      end: `${year}-${pad(endMonth + 1)}-${pad(lastDayOfMonth(year, endMonth))}`,
    };
  }
  const month = Math.min(12, Math.max(1, unit));
  const monthNames = de ? MONTH_NAMES_DE : MONTH_NAMES_EN;
  return {
    mode: "month",
    key: `${year}-${pad(month)}`,
    label: `${monthNames[month - 1]} ${year}`,
    start: `${year}-${pad(month)}-01`,
    end: `${year}-${pad(month)}-${pad(lastDayOfMonth(year, month - 1))}`,
  };
}

/** Periode des aktuellen Monats. */
export function currentMonthPeriod(now: Date = new Date(), isDe: boolean = false): Period {
  return buildPeriod("month", now.getFullYear(), now.getMonth() + 1, isDe);
}

/** Lokalisierte Beschriftung für einen Perioden-Schlüssel (`2026-08` → `August 2026`). */
export function periodLabelFromKey(key: string, isDe: boolean = false): string {
  const month = /^(\d{4})-(\d{2})$/.exec(key);
  if (month) {
    const monthNames = isDe ? MONTH_NAMES_DE : MONTH_NAMES_EN;
    return `${monthNames[Number(month[2]) - 1]} ${month[1]}`;
  }
  const quarter = /^(\d{4})-Q([1-4])$/.exec(key);
  if (quarter) return `Q${quarter[2]} ${quarter[1]}`;
  if (/^\d{4}$/.test(key)) return isDe ? `Jahr ${key}` : `Year ${key}`;
  return key;
}

/** `true`, wenn das Belegdatum in die Periode fällt (`all` = immer). */
export function isInPeriod(
  receipt: Partial<ProcessedReceipt> | null | undefined,
  period: Period
): boolean {
  if (period.mode === "all" || !period.start || !period.end) return true;
  const iso = receipt?.date?.isoDate;
  if (!iso || typeof iso !== "string") return false;
  return iso >= period.start && iso <= period.end;
}

/** Filtert eine Belegliste auf eine Periode. */
export function filterByPeriod<T extends Partial<ProcessedReceipt>>(
  receipts: T[],
  period: Period
): T[] {
  if (period.mode === "all") return receipts;
  return receipts.filter((receipt) => isInPeriod(receipt, period));
}

/**
 * Sammelt alle Perioden, für die tatsächlich Belege existieren – absteigend
 * (neueste zuerst). Der aktuelle Monat ist immer enthalten, damit der
 * Umschalter auch bei leerer Datenbank benutzbar bleibt.
 */
export function availablePeriods(
  receipts: Array<Partial<ProcessedReceipt>>,
  mode: PeriodMode,
  nowOrIsDe?: Date | boolean,
  isDe: boolean = false
): Period[] {
  const now = nowOrIsDe instanceof Date ? nowOrIsDe : new Date();
  const de = typeof nowOrIsDe === "boolean" ? nowOrIsDe : isDe;
  if (mode === "all") return [buildPeriod("all", now.getFullYear(), 1, de)];

  const keys = new Map<string, Period>();
  const addFromDate = (year: number, month: number) => {
    const period =
      mode === "year"
        ? buildPeriod("year", year, 1, de)
        : mode === "quarter"
          ? buildPeriod("quarter", year, Math.floor((month - 1) / 3) + 1, de)
          : buildPeriod("month", year, month, de);
    if (!keys.has(period.key)) keys.set(period.key, period);
  };

  addFromDate(now.getFullYear(), now.getMonth() + 1);

  for (const receipt of receipts) {
    const iso = receipt?.date?.isoDate;
    const match = typeof iso === "string" ? /^(\d{4})-(\d{2})/.exec(iso) : null;
    if (!match) continue;
    addFromDate(Number(match[1]), Number(match[2]));
  }

  return Array.from(keys.values()).sort((a, b) => (a.key < b.key ? 1 : -1));
}
