import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { SessionMetadataStore } from "../../src/sessions/metadataStore.js";

describe("SessionMetadataStore", () => {
  let tempDir: string;
  let store: SessionMetadataStore;

  beforeEach(async () => {
    tempDir = await mkdtemp(join(tmpdir(), "tjwater-session-"));
    store = new SessionMetadataStore(tempDir);
    await store.initialize();
  });

  afterEach(async () => {
    await rm(tempDir, { force: true, recursive: true });
  });

  it("persists the provided opencode session id", async () => {
    const { record, created } = await store.ensure({
      actorKey: "actor-1",
      projectId: "project-1",
      projectKey: "project-key-1",
      sessionId: "opencode-session-1",
      userId: "user-1",
    });

    expect(created).toBe(true);
    expect(record.sessionId).toBe("opencode-session-1");
    expect(record.ownerUserId).toBe("user-1");
    expect(record.status).toBe("active");
  });

  it("touches metadata and preserves scoped ownership", async () => {
    const { record } = await store.ensure({
      actorKey: "actor-2",
      projectId: "project-2",
      projectKey: "project-key-2",
      sessionId: "existing-session",
      userId: "user-2",
    });

    const touched = await store.touch(record, {
      title: "新标题",
    });

    expect(touched.title).toBe("新标题");
    expect(touched.updatedAt >= record.updatedAt).toBe(true);

    const fetched = await store.get(
      {
        actorKey: "actor-2",
        projectId: "project-2",
        projectKey: "project-key-2",
        userId: "user-2",
      },
      "existing-session",
    );
    expect(fetched?.title).toBe("新标题");
  });

  it("does not expose a session across users or projects", async () => {
    await store.ensure({
      actorKey: "actor-a",
      projectId: "project-a",
      projectKey: "project-key-a",
      sessionId: "private-session",
      userId: "user-a",
    });

    expect(
      await store.get(
        {
          actorKey: "actor-b",
          projectId: "project-a",
          projectKey: "project-key-a",
          userId: "user-b",
        },
        "private-session",
      ),
    ).toBeNull();
    expect(
      await store.get(
        {
          actorKey: "actor-a",
          projectId: "project-b",
          projectKey: "project-key-b",
          userId: "user-a",
        },
        "private-session",
      ),
    ).toBeNull();
  });

  it("rejects idempotent creation when the session belongs to another scope", async () => {
    await store.ensure({
      actorKey: "actor-a",
      projectId: "project-a",
      projectKey: "project-key-a",
      sessionId: "claimed-session",
      userId: "user-a",
    });

    await expect(
      store.ensure({
        actorKey: "actor-b",
        projectId: "project-b",
        projectKey: "project-key-b",
        sessionId: "claimed-session",
        userId: "user-b",
      }),
    ).rejects.toThrow("session metadata ownership mismatch");
  });

  it("keeps long session ids with the same slug prefix distinct", async () => {
    const prefix = "s".repeat(64);
    const firstSessionId = `${prefix}-first`;
    const secondSessionId = `${prefix}-second`;
    const context = {
      actorKey: "actor-long",
      projectId: "project-long",
      projectKey: "project-key-long",
      userId: "user-long",
    };

    await store.ensure({ ...context, sessionId: firstSessionId });
    await store.ensure({ ...context, sessionId: secondSessionId });

    expect((await store.get(context, firstSessionId))?.sessionId).toBe(
      firstSessionId,
    );
    expect((await store.get(context, secondSessionId))?.sessionId).toBe(
      secondSessionId,
    );
  });
});
