import { describe, expect, it } from "vitest"

import type { Fahrt, Tankvorgang } from "../api"
import {
  fahrtenSeit,
  istNachtZeit,
  jahresauswertung,
  langzeitverbrauch,
  nachJahrUndMonat,
  tankSeit,
  zeitraumStart,
} from "./statistik"

const JETZT = new Date("2026-08-11T12:00:00")

function fahrt(teil: Partial<Fahrt> & { trip_id: string; ts_start: string }): Fahrt {
  return {
    ts_end: teil.ts_start,
    duration_s: 1800,
    distance_km: 10,
    km_quelle: "odometer",
    odo_start: null,
    odo_end: null,
    art: "privat",
    source: "ha",
    status: "vollständig",
    ...teil,
  }
}

function tankung(teil: Partial<Tankvorgang> & { tank_id: string; ts: string }): Tankvorgang {
  return {
    liters: 40,
    fuel_total_eur: 70,
    price_per_l: 1.75,
    source: "manual",
    status: "vollständig",
    ...teil,
  }
}

/** Ortszeit-Datum. toISOString() waere UTC und kippt in Zeitzonen oestlich
    von Greenwich auf den Vortag - der Code rechnet aber bewusst lokal. */
function alsTag(d: Date): string {
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`
}

describe("zeitraumStart", () => {
  it("beginnt das Jahr am 1. Januar", () => {
    expect(alsTag(zeitraumStart("jahr", JETZT))).toBe("2026-01-01")
  })

  it("beginnt den Monat am Ersten", () => {
    expect(alsTag(zeitraumStart("monat", JETZT))).toBe("2026-08-01")
  })

  it("beginnt die Woche am Montag, nicht am Sonntag", () => {
    // Der 11.08.2026 ist ein Dienstag, die Woche beginnt am 10.08.
    const start = zeitraumStart("woche", JETZT)
    expect(start.getDay()).toBe(1)
    expect(start.getDate()).toBe(10)
  })

  it("beginnt den Tag um Mitternacht", () => {
    const start = zeitraumStart("tag", JETZT)
    expect(start.getHours()).toBe(0)
    expect(start.getDate()).toBe(11)
  })
})

describe("istNachtZeit", () => {
  it("erkennt eine Spanne über Mitternacht", () => {
    expect(istNachtZeit("2026-08-11T23:30:00", "22:00", "06:00")).toBe(true)
    expect(istNachtZeit("2026-08-11T03:00:00", "22:00", "06:00")).toBe(true)
    expect(istNachtZeit("2026-08-11T12:00:00", "22:00", "06:00")).toBe(false)
  })

  it("erkennt eine Spanne innerhalb eines Tages", () => {
    expect(istNachtZeit("2026-08-11T13:00:00", "12:00", "14:00")).toBe(true)
    expect(istNachtZeit("2026-08-11T15:00:00", "12:00", "14:00")).toBe(false)
  })

  it("schließt den Beginn ein und das Ende aus", () => {
    expect(istNachtZeit("2026-08-11T22:00:00", "22:00", "06:00")).toBe(true)
    expect(istNachtZeit("2026-08-11T06:00:00", "22:00", "06:00")).toBe(false)
  })
})

describe("fahrtenSeit und tankSeit", () => {
  const fahrten = [
    fahrt({ trip_id: "heute", ts_start: "2026-08-11T09:00:00" }),
    fahrt({ trip_id: "diesewoche", ts_start: "2026-08-10T09:00:00" }),
    fahrt({ trip_id: "diesenmonat", ts_start: "2026-08-02T09:00:00" }),
    fahrt({ trip_id: "letztesjahr", ts_start: "2025-12-30T09:00:00" }),
    fahrt({ trip_id: "ohnestrecke", ts_start: "2026-08-11T10:00:00", distance_km: null }),
  ]

  it("filtert je Zeitraum", () => {
    expect(fahrtenSeit(fahrten, "tag", JETZT).map((f) => f.trip_id)).toEqual(["heute"])
    expect(fahrtenSeit(fahrten, "woche", JETZT).map((f) => f.trip_id)).toEqual([
      "heute",
      "diesewoche",
    ])
    expect(fahrtenSeit(fahrten, "jahr", JETZT)).toHaveLength(3)
  })

  it("lässt Fahrten ohne Strecke außen vor — sie würden jede Summe verfälschen", () => {
    expect(fahrtenSeit(fahrten, "tag", JETZT).some((f) => f.trip_id === "ohnestrecke")).toBe(false)
  })

  it("lässt Tankungen ohne Menge außen vor", () => {
    const tanks = [
      tankung({ tank_id: "voll", ts: "2026-08-11T09:00:00" }),
      tankung({ tank_id: "offen", ts: "2026-08-11T10:00:00", liters: null }),
    ]
    expect(tankSeit(tanks, "tag", JETZT).map((t) => t.tank_id)).toEqual(["voll"])
  })
})

describe("langzeitverbrauch", () => {
  it("rechnet bevorzugt mit dem Kilometerstand an den Tankvorgängen", () => {
    const tanks = [
      tankung({ tank_id: "a", ts: "2026-06-01T10:00:00", liters: 50, odometer_km: 40000 }),
      tankung({ tank_id: "b", ts: "2026-07-01T10:00:00", liters: 50, odometer_km: 40600 }),
    ]
    const ergebnis = langzeitverbrauch([], tanks)
    expect(ergebnis.km).toBe(600)
    expect(ergebnis.liter).toBe(100)
    // 100 l auf 600 km sind 16,7 l/100 km — plausibel.
    expect(ergebnis.wert).toBeCloseTo(16.67, 1)
    expect(ergebnis.basis).toBe("Kilometerstand an den Tankvorgängen")
  })

  it("weicht auf die erfassten Fahrten aus, wenn Kilometerstände fehlen", () => {
    const tanks = [tankung({ tank_id: "a", ts: "2026-07-01T10:00:00", liters: 50 })]
    const fahrten = [fahrt({ trip_id: "f", ts_start: "2026-06-01T10:00:00", distance_km: 500 })]
    const ergebnis = langzeitverbrauch(fahrten, tanks)
    expect(ergebnis.km).toBe(500)
    expect(ergebnis.basis).toBe("erfasste Fahrten")
  })

  it("meldet unplausible Werte als unbekannt statt als Tatsache", () => {
    // 100 l auf 50 km wären 200 l/100 km - unmöglich.
    const tanks = [
      tankung({ tank_id: "a", ts: "2026-06-01T10:00:00", liters: 50, odometer_km: 40000 }),
      tankung({ tank_id: "b", ts: "2026-07-01T10:00:00", liters: 50, odometer_km: 40050 }),
    ]
    const ergebnis = langzeitverbrauch([], tanks)
    expect(ergebnis.wert).toBeNull()
    expect(ergebnis.unplausibel).toBe(true)
  })

  it("kommt ohne Tankvorgänge zurecht", () => {
    const ergebnis = langzeitverbrauch([], [])
    expect(ergebnis.wert).toBeNull()
    expect(ergebnis.unplausibel).toBe(false)
    expect(ergebnis.bis).toBeNull()
  })
})

describe("jahresauswertung", () => {
  it("teilt die Strecke in Tag und Nacht sowie in Arbeitsweg und privat", () => {
    const fahrten = [
      fahrt({ trip_id: "tag", ts_start: "2026-08-05T12:00:00", distance_km: 100, art: "arbeitsweg" }),
      fahrt({ trip_id: "nacht", ts_start: "2026-08-05T23:00:00", distance_km: 40 }),
    ]
    const a = jahresauswertung(fahrten, [], "22:00", "06:00", JETZT)
    expect(a.kmJahr).toBe(140)
    expect(a.tagKm).toBe(100)
    expect(a.nachtKm).toBe(40)
    expect(a.arbeitKm).toBe(100)
    expect(a.privatKm).toBe(40)
    expect(a.arbeitFahrten).toBe(1)
    expect(a.privatFahrten).toBe(1)
  })
})

describe("nachJahrUndMonat", () => {
  it("gruppiert absteigend, Neuestes zuerst", () => {
    const eintraege = [
      { id: "alt", ts: "2025-03-04T10:00:00" },
      { id: "neu", ts: "2026-08-01T10:00:00" },
      { id: "mittel", ts: "2026-02-01T10:00:00" },
    ]
    const gruppen = nachJahrUndMonat(eintraege, (e) => e.ts)
    expect(gruppen.map((g) => g.jahr)).toEqual([2026, 2025])
    expect(gruppen[0]?.monate.map((m) => m.monat)).toEqual([7, 1])
    expect(gruppen[0]?.eintraege.map((e) => e.id)).toEqual(["neu", "mittel"])
  })

  it("überspringt unlesbare Zeitangaben, statt daran zu scheitern", () => {
    const gruppen = nachJahrUndMonat(
      [{ id: "kaputt", ts: "keine-zeit" }, { id: "gut", ts: "2026-08-01T10:00:00" }],
      (e) => e.ts,
    )
    expect(gruppen).toHaveLength(1)
    expect(gruppen[0]?.eintraege.map((e) => e.id)).toEqual(["gut"])
  })
})
