import { describe, expect, it } from "vitest";
import { CarTelemetryStore } from "../src/car-telemetry.js";
import { openDatabase } from "../src/database.js";
import type { CarTelemetry } from "../src/adb.js";

function sample(
  recordedAt: number,
  values: { speed?: number; soc?: number; range?: number; charging?: number; volts?: number } = {}
): CarTelemetry {
  const available = <T>(value: T) => ({ available: true as const, value });
  const missing = { available: false as const, reason: "Unavailable in test" };
  return {
    available: true,
    collectedAt: new Date(recordedAt).toISOString(),
    speedKph: values.speed === undefined ? missing : available(values.speed),
    gear: available("4"),
    wheelAngleDegrees: available(0),
    batteryPercent: values.soc === undefined ? missing : available(values.soc),
    rangeKm: values.range === undefined ? missing : available(values.range),
    batteryVoltage: values.volts === undefined ? missing : available(values.volts),
    totalConsumptionKwh: available(4.2),
    chargingStatus: values.charging === undefined ? missing : available(values.charging)
  };
}

describe("car telemetry history", () => {
  it("keeps bounded raw samples, hourly rollups, lifetime records, and an observed range estimate", () => {
    const database = openDatabase(":memory:");
    const store = new CarTelemetryStore(database);
    const startedAt = Date.now() - 2 * 60 * 60_000;

    store.record(sample(startedAt, { speed: 0, soc: 80, range: 320, charging: 0, volts: 398 }));
    for (let index = 1; index <= 400; index += 1) {
      store.record(sample(startedAt + index * 10_000, {
        speed: 100,
        soc: 80 - index * 0.02,
        range: 320 - index * 0.3,
        charging: 0,
        volts: 390
      }));
    }

    const chargingAt = startedAt + 401 * 10_000;
    for (let index = 0; index <= 13; index += 1) {
      store.record(sample(chargingAt + index * 10_000, {
        speed: 0,
        soc: 60 + index * 0.2,
        range: 240 + index,
        charging: 1,
        volts: 405
      }));
    }

    const overview = store.overview();
    expect(overview.records.peakSpeedKph).toBe(100);
    expect(overview.records.peakChargingPowerKw).toBeGreaterThan(30);
    expect(overview.records.maxObservedRangeKm).toBe(320);
    expect(overview.lifetime.trackedDistanceKm).toBeGreaterThan(10);
    expect(overview.lifetime.ownRangeEstimateKm).toBeGreaterThan(100);
    expect(overview.lifetime.chargeSessions).toBe(1);
    expect(overview.storage.rawSamples).toBe(415);
    expect(store.history("24h").points.length).toBeGreaterThan(1);
    expect(store.history("all").points.length).toBeGreaterThan(0);

    store.prune(chargingAt + 91 * 24 * 60 * 60_000);
    const pruned = store.overview();
    expect(pruned.storage.rawSamples).toBe(0);
    expect(pruned.records.peakSpeedKph).toBe(100);
    expect(pruned.lifetime.trackedDistanceKm).toBeGreaterThan(10);
    database.close();
  });

  it("leaves estimates empty until enough real observations exist", () => {
    const database = openDatabase(":memory:");
    const store = new CarTelemetryStore(database);
    store.record(sample(Date.now(), { speed: 0, soc: 75, range: 280, charging: 0 }));
    expect(store.overview().lifetime.ownRangeEstimateKm).toBeNull();
    expect(store.history("24h").points).toHaveLength(1);
    database.close();
  });

  it("clears history independently from all-time statistics", () => {
    const database = openDatabase(":memory:");
    const store = new CarTelemetryStore(database);
    const startedAt = Date.now() - 10 * 60_000;

    store.record(sample(startedAt, { speed: 20, soc: 80, range: 320, charging: 0 }));
    for (let index = 1; index <= 40; index += 1) {
      store.record(sample(startedAt + index * 10_000, {
        speed: 110,
        soc: 80 - index * 0.15,
        range: 320 - index,
        charging: 0
      }));
    }

    const before = store.overview();
    expect(before.lifetime.trackedDistanceKm).toBeGreaterThan(10);
    expect(before.lifetime.ownRangeEstimateKm).not.toBeNull();

    expect(store.clear("history")).toMatchObject({ mode: "history", deletedSamples: 41 });
    const kept = store.overview();
    expect(store.history("24h").points).toEqual([]);
    expect(kept.storage.rawSamples).toBe(0);
    expect(kept.storage.hourlyRollups).toBe(0);
    expect(kept.records).toEqual(before.records);
    expect(kept.lifetime).toEqual(before.lifetime);

    store.record(sample(Date.now(), { speed: 30, soc: 70, range: 260, charging: 0 }));
    expect(store.clear("all")).toMatchObject({ mode: "all", deletedSamples: 1 });
    const reset = store.overview();
    expect(store.history("all").points).toEqual([]);
    expect(reset.records).toEqual({
      peakSpeedKph: null,
      peakSpeedAt: null,
      peakChargingPowerKw: null,
      peakChargingPowerAt: null,
      maxObservedRangeKm: null,
      maxObservedRangeAt: null
    });
    expect(reset.lifetime).toMatchObject({
      firstSampleAt: null,
      lastSampleAt: null,
      sampleCount: 0,
      trackedDistanceKm: 0,
      drivingSeconds: 0,
      chargingSeconds: 0,
      chargeSessions: 0,
      socUsedPercent: 0,
      averageMovingSpeedKph: null,
      ownRangeEstimateKm: null
    });
    database.close();
  });
});
