/**
 * Auswertung von Fahrten und Tankvorgängen.
 *
 * Aus dem alten Panel portiert (`periodStart`, `fahrtenSeit`, `tankSeit`,
 * `istNachtZeit`, `langzeitverbrauch`). Die Rechenregeln sind bewusst
 * unverändert — sie sind über mehrere Runden entstanden und begründet.
 */

import type { Fahrt, Tankvorgang } from "../api"
import { summe } from "../format"

export type Zeitraum = "jahr" | "monat" | "woche" | "tag"

export const ZEITRAEUME: Zeitraum[] = ["jahr", "monat", "woche", "tag"]

export const ZEITRAUM_NAME: Record<Zeitraum, string> = {
  jahr: "Jahr",
  monat: "Monat",
  woche: "Woche",
  tag: "Tag",
}

/** Beginn des Zeitraums. Die Woche startet montags, nicht sonntags. */
export function zeitraumStart(art: Zeitraum, jetzt = new Date()): Date {
  if (art === "jahr") return new Date(jetzt.getFullYear(), 0, 1)
  if (art === "monat") return new Date(jetzt.getFullYear(), jetzt.getMonth(), 1)
  const d = new Date(jetzt)
  d.setHours(0, 0, 0, 0)
  if (art === "woche") d.setDate(d.getDate() - ((d.getDay() + 6) % 7))
  return d
}

export function fahrtenSeit(fahrten: readonly Fahrt[], art: Zeitraum, jetzt = new Date()): Fahrt[] {
  const start = zeitraumStart(art, jetzt)
  return fahrten.filter((f) => f.distance_km != null && new Date(f.ts_start) >= start)
}

export function tankSeit(
  tankvorgaenge: readonly Tankvorgang[],
  art: Zeitraum,
  jetzt = new Date(),
): Tankvorgang[] {
  const start = zeitraumStart(art, jetzt)
  return tankvorgaenge.filter((t) => t.liters != null && new Date(t.ts) >= start)
}

/**
 * Liegt der Zeitpunkt in der Nachtspanne? Die Spanne darf über Mitternacht
 * gehen (22:00 bis 06:00) — dann gilt sie außerhalb statt innerhalb.
 */
export function istNachtZeit(zeitpunkt: string | Date, von: string, bis: string): boolean {
  const d = zeitpunkt instanceof Date ? zeitpunkt : new Date(zeitpunkt)
  const uhrzeit = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`
  return von > bis ? uhrzeit >= von || uhrzeit < bis : uhrzeit >= von && uhrzeit < bis
}

export interface Langzeitverbrauch {
  /** null, wenn kein plausibler Wert bestimmbar ist. */
  wert: number | null
  km: number
  liter: number
  bis: Date | null
  basis: "Kilometerstand an den Tankvorgängen" | "erfasste Fahrten" | "–"
  /** Ein Wert kam heraus, lag aber außerhalb des Plausibilitätsfensters. */
  unplausibel: boolean
}

/**
 * Alle je getankten Liter gegen die Strecke bis zum letzten Tankstop.
 *
 * Bewusst nicht „Liter des Zeitraums geteilt durch km des Zeitraums": das ergab
 * unmögliche Werte, sobald eine Fahrt fehlte oder ein Tankvorgang noch
 * unvollständig war. Werte außerhalb von 3 bis 30 l/100 km gelten als
 * unbekannt statt als Tatsache.
 */
export function langzeitverbrauch(
  fahrten: readonly Fahrt[],
  tankvorgaenge: readonly Tankvorgang[],
): Langzeitverbrauch {
  const tanks = tankvorgaenge
    .filter((t) => t.liters != null)
    .slice()
    .sort((a, b) => new Date(a.ts).getTime() - new Date(b.ts).getTime())

  if (tanks.length === 0) {
    return { wert: null, km: 0, liter: 0, bis: null, basis: "–", unplausibel: false }
  }

  const letzter = tanks[tanks.length - 1]!
  const bis = new Date(letzter.ts)
  const liter = summe(tanks, (t) => t.liters ?? 0)

  // Bevorzugt der Kilometerstand an den Tankvorgängen selbst — er zählt auch
  // Strecke mit, für die keine Fahrt aufgezeichnet wurde.
  const mitOdo = tanks.filter((t) => t.odometer_km != null)
  let km: number
  let basis: Langzeitverbrauch["basis"]
  if (mitOdo.length >= 2) {
    km = (mitOdo[mitOdo.length - 1]!.odometer_km ?? 0) - (mitOdo[0]!.odometer_km ?? 0)
    basis = "Kilometerstand an den Tankvorgängen"
  } else {
    km = summe(
      fahrten.filter((f) => f.distance_km != null && new Date(f.ts_start) <= bis),
      (f) => f.distance_km ?? 0,
    )
    basis = "erfasste Fahrten"
  }

  const roh = km > 0 ? (liter / km) * 100 : null
  const gueltig = roh !== null && roh >= 3 && roh <= 30
  return {
    wert: gueltig ? roh : null,
    km,
    liter,
    bis,
    basis,
    unplausibel: roh !== null && !gueltig,
  }
}

export interface Jahresauswertung {
  jahr: number
  kmJahr: number
  stundenJahr: number
  literJahr: number
  kostenJahr: number
  tagKm: number
  nachtKm: number
  arbeitKm: number
  privatKm: number
  arbeitFahrten: number
  privatFahrten: number
  jeZeitraum: Record<Zeitraum, { km: number; stunden: number; liter: number; kosten: number }>
  langzeit: Langzeitverbrauch
}

export function jahresauswertung(
  fahrten: readonly Fahrt[],
  tankvorgaenge: readonly Tankvorgang[],
  nachtVon: string,
  nachtBis: string,
  jetzt = new Date(),
): Jahresauswertung {
  const jeZeitraum = {} as Jahresauswertung["jeZeitraum"]
  for (const art of ZEITRAEUME) {
    const f = fahrtenSeit(fahrten, art, jetzt)
    const t = tankSeit(tankvorgaenge, art, jetzt)
    jeZeitraum[art] = {
      km: summe(f, (x) => x.distance_km ?? 0),
      stunden: summe(f, (x) => x.duration_s ?? 0) / 3600,
      liter: summe(t, (x) => x.liters ?? 0),
      kosten: summe(t, (x) => x.fuel_total_eur ?? 0),
    }
  }

  const fJahr = fahrtenSeit(fahrten, "jahr", jetzt)
  const kmJahr = jeZeitraum.jahr.km
  const tagKm = summe(
    fJahr.filter((f) => !istNachtZeit(f.ts_start, nachtVon, nachtBis)),
    (f) => f.distance_km ?? 0,
  )
  const arbeit = fJahr.filter((f) => f.art === "arbeitsweg")
  const arbeitKm = summe(arbeit, (f) => f.distance_km ?? 0)

  return {
    jahr: jetzt.getFullYear(),
    kmJahr,
    stundenJahr: jeZeitraum.jahr.stunden,
    literJahr: jeZeitraum.jahr.liter,
    kostenJahr: jeZeitraum.jahr.kosten,
    tagKm,
    nachtKm: kmJahr - tagKm,
    arbeitKm,
    privatKm: kmJahr - arbeitKm,
    arbeitFahrten: arbeit.length,
    privatFahrten: fJahr.length - arbeit.length,
    jeZeitraum,
    langzeit: langzeitverbrauch(fahrten, tankvorgaenge),
  }
}

/* ------------------------------------------------- Gruppierung für Listen */

export interface MonatsGruppe<T> {
  /** Format YYYY-MM, damit sich absteigend sortieren lässt. */
  schluessel: string
  jahr: number
  monat: number
  eintraege: T[]
}

export interface JahresGruppe<T> {
  jahr: number
  monate: MonatsGruppe<T>[]
  eintraege: T[]
}

/**
 * Gruppiert nach Jahr und Monat, beide absteigend — das Neueste zuerst,
 * so wie es die Listenansichten von Fahrten und Tankvorgängen zeigen.
 */
export function nachJahrUndMonat<T>(
  eintraege: readonly T[],
  zeitpunktVon: (eintrag: T) => string,
): JahresGruppe<T>[] {
  const jahre = new Map<number, Map<string, MonatsGruppe<T>>>()

  const sortiert = eintraege
    .slice()
    .sort((a, b) => new Date(zeitpunktVon(b)).getTime() - new Date(zeitpunktVon(a)).getTime())

  for (const eintrag of sortiert) {
    const d = new Date(zeitpunktVon(eintrag))
    if (Number.isNaN(d.getTime())) continue
    const jahr = d.getFullYear()
    const monat = d.getMonth()
    const schluessel = `${jahr}-${String(monat + 1).padStart(2, "0")}`

    let monate = jahre.get(jahr)
    if (!monate) {
      monate = new Map()
      jahre.set(jahr, monate)
    }
    let gruppe = monate.get(schluessel)
    if (!gruppe) {
      gruppe = { schluessel, jahr, monat, eintraege: [] }
      monate.set(schluessel, gruppe)
    }
    gruppe.eintraege.push(eintrag)
  }

  return [...jahre.entries()]
    .sort((a, b) => b[0] - a[0])
    .map(([jahr, monate]) => {
      const sortierteMonate = [...monate.values()].sort((a, b) => b.monat - a.monat)
      return {
        jahr,
        monate: sortierteMonate,
        eintraege: sortierteMonate.flatMap((m) => m.eintraege),
      }
    })
}
