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

const { hydrateStoreFromSnapshot } = await import("../src/dimsdk/cli/persistence/snapshot.ts");
const { mergeSessionUiMetadata } = await import("../src/dimsdk/core/session-meta.ts");
const { createDimSdkStore } = await import("../src/dimsdk/core/store.ts");
const { useChatSessionMeta } = await import("../src/dimsdk/pages/ChatPage/logic/session-meta.ts");
const { createDimSdkPersistence } = await import("../src/dimsdk/cli/persistence.ts");

function createStore() {
  const agent = {
    async *receiveMessage() {
      yield { content: "", metadata: { done: true }, role: "assistant" };
    },
  };

  return createDimSdkStore({
    agent: agent as never,
    context: { cwd: ".", env: {}, system: "test" },
    toolRunner: async () => ({ id: "tool", output: "ok", status: "success" as const }),
  });
}

function applySessionMeta(store: ReturnType<typeof createStore>, patch: Parameters<typeof mergeSessionUiMetadata>[1]) {
  const next = mergeSessionUiMetadata(store.state.sessionMeta, patch);
  store.state.sessionMeta.queue = [...next.queue];
  store.state.sessionMeta.queueLength = next.queueLength;
  store.state.sessionMeta.status = next.status;
  store.state.sessionMeta.lastUpdatedAt = next.lastUpdatedAt;
  store.state.sessionMeta.lastErrorCode = next.lastErrorCode;
  store.state.sessionMeta.pinnedMessageIds = [...next.pinnedMessageIds];
  store.state.sessionMeta.titleSuggestedAt = next.titleSuggestedAt;
}

describe("dimsdk session metadata compatibility", () => {
  it("keeps newer local queue metadata on same-session hydrate", () => {
    const store = createStore();
    store.state.sessionId = "session_1";

    applySessionMeta(store, {
      lastUpdatedAt: 300,
      queue: [
        { createdAt: 3, id: "q3", kind: "user", text: "3" },
        { createdAt: 4, id: "q4", kind: "user", text: "4" },
        { createdAt: 5, id: "q5", kind: "user", text: "5" },
      ],
      queueLength: 3,
      status: "running",
    });

    hydrateStoreFromSnapshot(store as never, {
      context: {
        messages: [{ content: "12", role: "user" }],
      },
      id: "session_1",
      metadata: {
        ui: {
          lastUpdatedAt: 200,
          queue: [
            { createdAt: 1, id: "q1", kind: "steer", steeredAt: 10, text: "1" },
            { createdAt: 2, id: "q2", kind: "steer", steeredAt: 20, text: "2" },
          ],
          queueLength: 2,
          status: "idle",
        },
      },
      state: { updatedAt: Date.now() },
    } as never);

    expect(store.state.messages.map((message) => message.role === "user" ? message.content : message.role)).toEqual(["12"]);
    expect(store.state.sessionMeta.queue.map((item) => item.id)).toEqual(["q3", "q4", "q5"]);
    expect(store.state.sessionMeta.status).toBe("running");
    expect(store.state.sessionMeta.lastUpdatedAt).toBe(300);
  });

  it("applies newer snapshot queue metadata on same-session hydrate", () => {
    const store = createStore();
    store.state.sessionId = "session_1";

    applySessionMeta(store, {
      lastUpdatedAt: 200,
      queue: [{ createdAt: 3, id: "q3", kind: "user", text: "3" }],
      queueLength: 1,
      status: "running",
    });

    hydrateStoreFromSnapshot(store as never, {
      context: {
        messages: [{ content: "12", role: "user" }],
      },
      id: "session_1",
      metadata: {
        ui: {
          lastUpdatedAt: 300,
          queue: [
            { createdAt: 4, id: "q4", kind: "user", text: "4" },
            { createdAt: 5, id: "q5", kind: "user", text: "5" },
          ],
          queueLength: 2,
          status: "idle",
        },
      },
      state: { updatedAt: Date.now() },
    } as never);

    expect(store.state.sessionMeta.queue.map((item) => item.id)).toEqual(["q4", "q5"]);
    expect(store.state.sessionMeta.status).toBe("idle");
    expect(store.state.sessionMeta.lastUpdatedAt).toBe(300);
  });

  it("persists queued session metadata while a turn is running and swallows persistence failures", async () => {
    vi.useFakeTimers();
    try {
      const store = createStore();
      store.state.sessionId = "session_1";
      store.state.runningAgentRound = true;
      let updateCalls = 0;
      const { updateSessionMeta } = useChatSessionMeta({
        sessionsApi: {
          async updateMetadata() {
            updateCalls += 1;
            throw new Error("THREAD_BUSY");
          },
        } as never,
        store: store as never,
      });

      updateSessionMeta({
        queue: [{ createdAt: 1, id: "q1", kind: "user", text: "queued" }],
        queueLength: 1,
        status: "running",
      });
      await vi.advanceTimersByTimeAsync(250);
      expect(updateCalls).toBe(1);
    } finally {
      vi.useRealTimers();
    }
  });

  it("keeps locally queued metadata after a running turn settles and a stale snapshot arrives", async () => {
    vi.useFakeTimers();
    try {
      const store = createStore();
      store.state.sessionId = "session_1";
      store.state.runningAgentRound = true;
      const updatePayloads: Record<string, unknown>[] = [];
      const { updateSessionMeta } = useChatSessionMeta({
        sessionsApi: {
          async updateMetadata(_id, metadata) {
            updatePayloads.push(metadata);
          },
        } as never,
        store: store as never,
      });

      updateSessionMeta({
        queue: [
          { createdAt: 10, id: "q10", kind: "user", text: "first" },
          { createdAt: 11, id: "q11", kind: "user", text: "second" },
          { createdAt: 12, id: "q12", kind: "user", text: "third" },
        ],
        queueLength: 3,
        status: "running",
      });
      await vi.advanceTimersByTimeAsync(250);

      expect(updatePayloads).toHaveLength(1);
      expect(updatePayloads[0]).toMatchObject({
        lastErrorCode: undefined,
        queue: [
          { createdAt: 10, id: "q10", kind: "user", text: "first" },
          { createdAt: 11, id: "q11", kind: "user", text: "second" },
          { createdAt: 12, id: "q12", kind: "user", text: "third" },
        ],
        queueLength: 3,
        status: "running",
      });

      const localUpdatedAt = store.state.sessionMeta.lastUpdatedAt;
      expect(localUpdatedAt).toBeGreaterThan(0);

      store.state.runningAgentRound = false;

      hydrateStoreFromSnapshot(store as never, {
        context: {
          messages: [{ content: "done", role: "assistant" }],
        },
        id: "session_1",
        metadata: {
          ui: {
            lastUpdatedAt: localUpdatedAt - 1,
            queue: [],
            queueLength: 0,
            status: "idle",
          },
        },
        state: { updatedAt: Date.now() },
      } as never);

      expect(store.state.sessionMeta.queue.map((item) => item.id)).toEqual([
        "q10",
        "q11",
        "q12",
      ]);
      expect(store.state.sessionMeta.queueLength).toBe(3);
      expect(store.state.sessionMeta.status).toBe("running");
      expect(store.state.sessionMeta.lastUpdatedAt).toBe(localUpdatedAt);
    } finally {
      vi.useRealTimers();
    }
  });

  it("restores usage counters from supplemental ui metadata when the runtime snapshot omits usage", () => {
    const store = createStore();

    hydrateStoreFromSnapshot(store as never, {
      context: {
        messages: [{ content: "done", role: "assistant" }],
      },
      id: "session_usage",
      metadata: {
        ui: {
          usage: {
            promptTokens: 1200,
            completionTokens: 34,
            totalTokens: 3172,
          },
        },
      },
      state: { updatedAt: Date.now() },
    } as never);

    expect(store.state.usage.prompt_tokens).toBe(1200);
    expect(store.state.usage.completion_tokens).toBe(34);
    expect(store.state.usage.total_tokens).toBe(3172);
  });

  it("persists supplemental usage metadata for session reopen", async () => {
    vi.useFakeTimers();
    try {
      const store = createStore();
      const metadataCalls: Record<string, unknown>[] = [];
      const persistence = createDimSdkPersistence({
        cacheFile: "/tmp/dimsdk-session-meta-cache.json",
        cache: { version: 1, updatedAt: 0 } as never,
        configFile: "/tmp/dimsdk-session-meta-config.json",
        config: { version: 1, updatedAt: 0 } as never,
        toolsFile: "/tmp/dimsdk-session-meta-tools.json",
        tools: { version: 1, updatedAt: 0 } as never,
        sessionsBackend: {
          async list() { return []; },
          async get() { return null; },
          async create() { return null as never; },
          async destroy() {},
          async updateMetadata(_id, metadata) {
            metadataCalls.push(metadata);
            return null;
          },
        } as never,
        deferPersist: true,
      });
      persistence.onStoreCreated(store);

      store.state.sessionId = "session_usage";
      store.state.runningAgentRound = false;
      store.state.usage.prompt_tokens = 1200;
      store.state.usage.completion_tokens = 34;
      store.state.usage.total_tokens = 3172;
      store.state.lastRequestBudget = {
        estimatedInputTokens: 3172,
        maxInputTokens: 32000,
        thresholdTokens: 25600,
        triggerRatio: 0.8,
        updatedAtMs: Date.now(),
        usedAutoCompaction: false,
      } as never;

      await vi.runAllTimersAsync();

      expect(metadataCalls.at(-1)).toMatchObject({
        ui: {
          usage: {
            promptTokens: 1200,
            completionTokens: 34,
            totalTokens: 3172,
          },
        },
      });
    } finally {
      vi.useRealTimers();
    }
  });
});
