import { afterEach, describe, expect, test } from "bun:test";
import { Database } from "bun:sqlite";
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { ReportBucket } from "@overdeck/report-contract";
import { ReportHistoryStore } from "./history-store";

const roots: string[] = [];

function databasePath(): string {
  const parent = join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "overdeck", "tests", "report-history");
  mkdirSync(parent, { recursive: true });
  const root = mkdtempSync(join(parent, "store-"));
  roots.push(root);
  return join(root, "history.sqlite");
}

function bucket(overrides: Partial<ReportBucket> = {}): ReportBucket {
  return {
    bucketStart: "2026-08-16T10:00:00.000Z",
    bucketEnd: "2026-08-16T11:00:00.000Z",
    metricKey: "activity.recorded-events",
    metricVersion: 1,
    label: "Recorded events",
    unit: "count",
    dimensionKey: "filters:{}",
    value: 4,
    coverage: { status: "complete", key: "complete", gaps: [] },
    sourceWatermark: { actions: "2026-08-16T10:45:00.000Z" },
    generatedAt: "2026-08-16T11:00:00.000Z",
    backfilled: true,
    ...overrides,
  };
}

afterEach(() => {
  while (roots.length > 0) rmSync(roots.pop()!, { recursive: true, force: true });
});

describe("ReportHistoryStore", () => {
  test("persists immutable buckets across restarts and treats an exact replay as unchanged", () => {
    const path = databasePath();
    const first = new ReportHistoryStore(path);
    expect(first.writeReportBucket(bucket())).toBe("inserted");
    expect(first.writeReportBucket(bucket())).toBe("unchanged");
    first.close();

    const reopened = new ReportHistoryStore(path);
    expect(reopened.readReportBuckets("2026-08-16T10:00:00.000Z", "2026-08-16T11:00:00.000Z").buckets).toEqual([bucket()]);
    reopened.close();
  });

  test("rejects a changed payload at the same immutable key", () => {
    const store = new ReportHistoryStore(databasePath());
    store.writeReportBucket(bucket());
    expect(() => store.writeReportBucket(bucket({ value: 5 }))).toThrow("A different immutable report bucket already exists.");
    store.close();
  });

  test("keeps filter dimensions independent", () => {
    const store = new ReportHistoryStore(databasePath());
    store.writeReportBucket(bucket({ dimensionKey: "all", value: 8 }));
    store.writeReportBucket(bucket({ dimensionKey: "project:overdeck", value: 3 }));
    const values = store.readReportBuckets("2026-08-16T10:00:00.000Z", "2026-08-16T11:00:00.000Z").buckets
      .map((row) => [row.dimensionKey, row.value]);
    expect(values).toEqual([["all", 8], ["project:overdeck", 3]]);
    store.close();
  });

  test("prunes only rows older than retention", () => {
    const store = new ReportHistoryStore(databasePath(), { retentionDays: 1 });
    store.writeReportBucket(bucket({ bucketStart: "2026-08-14T10:00:00.000Z", bucketEnd: "2026-08-14T11:00:00.000Z", generatedAt: "2026-08-14T11:00:00.000Z" }));
    store.writeReportBucket(bucket());
    expect(store.prune(Date.parse("2026-08-16T12:00:00.000Z"))).toBe(1);
    expect(store.earliestAvailable()).toBe("2026-08-16T10:00:00.000Z");
    store.close();
  });

  test("refuses to overwrite a newer store schema", () => {
    const path = databasePath();
    const future = new Database(path, { create: true });
    future.exec("PRAGMA user_version = 2");
    future.close();
    expect(() => new ReportHistoryStore(path)).toThrow("Report history uses a newer schema version.");
  });

  test("quarantines corrupt rows without hiding healthy rows", () => {
    const path = databasePath();
    const store = new ReportHistoryStore(path);
    store.writeReportBucket(bucket());
    store.close();

    const mutable = new Database(path);
    mutable.query(`
      INSERT INTO report_buckets
        (bucket_start, bucket_end, metric_key, metric_version, dimension_key, payload_json)
      VALUES (?, ?, ?, ?, ?, ?)
    `).run("2026-08-16T10:00:00.000Z", "2026-08-16T11:00:00.000Z", "activity.failures", 1, "filters:{}", "{not-json");
    mutable.close();

    const reopened = new ReportHistoryStore(path);
    const result = reopened.readReportBuckets("2026-08-16T10:00:00.000Z", "2026-08-16T11:00:00.000Z");
    expect(result.buckets).toEqual([bucket()]);
    expect(result.quarantinedRows).toBe(1);
    expect(reopened.quarantineCount()).toBe(1);
    expect(reopened.readReportBuckets("2026-08-16T10:00:00.000Z", "2026-08-16T11:00:00.000Z").quarantinedRows).toBe(0);
    reopened.close();
  });
});
