import { existsSync, rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest";
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
import {
  DashboardPersistenceValidationError,
  createDashboardStore,
  type DashboardStore,
} from "./dashboard-store";

const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
const originalCwd = process.cwd();

afterEach(() => {
  vi.useRealTimers();
  process.chdir(originalCwd);
  stores.splice(0).forEach((store) => store.close());
  tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});

describe("dashboard persistence store", () => {
  test("seeds the first active dashboard and creates the sqlite file", async () => {
    const { dbPath, store } = await createTestStore();

    const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
      actor: "test-seed",
      message: "initial fixture",
    });

    expect(existsSync(dbPath)).toBe(true);
    expect(seed.operation).toBe("seed");
    expect(seed.actor).toBe("test-seed");
    expect(seed.schemaVersion).toBe(genericDashboardFixture.schemaVersion);
    expect(seed.document.metadata.title).toBe("Operations Console");

    const active = store.getActiveDashboard();
    expect(active?.currentRevisionId).toBe(seed.id);
    expect(active?.document.metadata.title).toBe("Operations Console");
    expect(store.listRevisions()).toHaveLength(1);
  });

  test("commits validated updates and records revision metadata", async () => {
    const { store } = await createTestStore();
    const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
      actor: "test-seed",
    });
    const updated = cloneDashboard({
      ...genericDashboardFixture,
      metadata: {
        ...genericDashboardFixture.metadata,
        title: "Operations Console Updated",
      },
    });

    const revision = store.commitDashboard(updated, {
      actor: "agent",
      message: "rename dashboard",
    });

    expect(revision.operation).toBe("commit");
    expect(revision.actor).toBe("agent");
    expect(revision.message).toBe("rename dashboard");
    expect(revision.document.metadata.title).toBe("Operations Console Updated");
    expect(store.getActiveDashboard()?.currentRevisionId).toBe(revision.id);
    expect(store.getRevision(seed.id)?.document.metadata.title).toBe("Operations Console");
    expect(store.listRevisions().map((item) => item.id)).toEqual([
      revision.id,
      seed.id,
    ]);
  });

  test("rejects invalid writes without changing the active revision", async () => {
    const { store } = await createTestStore();
    const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
      actor: "test-seed",
    });
    const invalid = cloneDashboard({
      ...genericDashboardFixture,
      layout: {
        ...genericDashboardFixture.layout,
        telemetry: ["missing-card"],
      },
    });

    expect(() =>
      store.commitDashboard(invalid, {
        actor: "agent",
        message: "invalid update",
      }),
    ).toThrow(DashboardPersistenceValidationError);

    const active = store.getActiveDashboard();
    expect(active?.currentRevisionId).toBe(seed.id);
    expect(active?.document.layout.telemetry).toEqual(genericDashboardFixture.layout.telemetry);
    expect(store.listRevisions()).toHaveLength(1);
  });

  test("loads specific revisions and rolls back to a prior valid document", async () => {
    const { store } = await createTestStore();
    const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
      actor: "test-seed",
    });
    const updated = cloneDashboard({
      ...genericDashboardFixture,
      metadata: {
        ...genericDashboardFixture.metadata,
        title: "Changed Dashboard",
      },
    });
    const change = store.commitDashboard(updated, {
      actor: "agent",
      message: "change title",
    });

    const rollback = store.rollbackToRevision(seed.id, {
      actor: "operator",
      message: "restore seed",
    });

    expect(store.getRevision(change.id)?.document.metadata.title).toBe("Changed Dashboard");
    expect(rollback.operation).toBe("rollback");
    expect(rollback.sourceRevisionId).toBe(seed.id);
    expect(rollback.document.metadata.title).toBe("Operations Console");
    expect(store.getActiveDashboard()?.currentRevisionId).toBe(rollback.id);
    expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console");
    expect(store.listRevisions().map((item) => item.operation)).toEqual([
      "rollback",
      "commit",
      "seed",
    ]);
  });

  test("assigns monotonic revision timestamps for stable history ordering", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-06-18T12:00:00.000Z"));
    const { store } = await createTestStore();
    const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
      actor: "test-seed",
    });
    const updated = cloneDashboard({
      ...genericDashboardFixture,
      metadata: {
        ...genericDashboardFixture.metadata,
        title: "Changed Dashboard",
      },
    });

    const commit = store.commitDashboard(updated, { actor: "agent" });
    const rollback = store.rollbackToRevision(seed.id, { actor: "operator" });

    expect([
      seed.createdAt.toISOString(),
      commit.createdAt.toISOString(),
      rollback.createdAt.toISOString(),
    ]).toEqual([
      "2026-06-18T12:00:00.000Z",
      "2026-06-18T12:00:00.001Z",
      "2026-06-18T12:00:00.002Z",
    ]);
    expect(store.listRevisions().map((item) => item.id)).toEqual([
      rollback.id,
      commit.id,
      seed.id,
    ]);
  });

  test("applies migrations when launched outside the repository root", async () => {
    const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-cwd-"));
    tempRoots.push(root);
    process.chdir(root);
    vi.resetModules();
    const { createDashboardStore: createStore } = await import("./dashboard-store");
    const store = createStore({
      databaseUrl: `file:${join(root, "dashboard.sqlite")}`,
    });
    stores.push(store);

    const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
      actor: "test-seed",
    });

    expect(seed.document.metadata.title).toBe("Operations Console");
    expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id);
  });
});

async function createTestStore() {
  const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-store-"));
  tempRoots.push(root);
  const dbPath = join(root, "nested", "dashboard.sqlite");
  const store = createDashboardStore({
    databaseUrl: `file:${dbPath}`,
  });
  stores.push(store);

  return { dbPath, store };
}

function cloneDashboard(document: DashboardDocument): DashboardDocument {
  return structuredClone(document);
}
