import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { tmpdir } from "node:os";
import { afterEach, describe, expect, it } from "vitest";
import {
  importLegacySessions,
  patchImportedSnapshot,
  RUNTIME_SESSION_METADATA_KEY,
} from "../src/dimsdk/cli/runtime-import.ts";
import type { LegacySessionPayload } from "../src/dimsdk/cli/runtime-import.ts";
import { runtimeSnapshotPath } from "../src/dimsdk/compat/runtime-host.ts";

const tempRoots: string[] = [];

afterEach(() => {
  while (tempRoots.length > 0) {
    rmSync(tempRoots.pop() as string, { force: true, recursive: true });
  }
});

describe("runtime import", () => {
  it("normalizes imported runtime payloads before materializing sessions", async () => {
    const root = mkdtempSync(path.join(tmpdir(), "best-agent-runtime-import-"));
    tempRoots.push(root);

    const activeStateDir = path.join(root, "active-state");
    const legacyStateDir = path.join(root, "legacy-state");
    const legacySessionDir = path.join(legacyStateDir, "session-alpha");
    await mkdir(legacySessionDir, { recursive: true });

    await writeFile(
      path.join(legacySessionDir, "session.json"),
      `${JSON.stringify({
        context: {
          messages: [{ content: "hello legacy", role: "user" }],
        },
        createdAt: 10,
        metadata: {
          cwd: "/repo/legacy",
          dimAgentRuntime: {
            mcpServers: [
              {
                args: ["server.js", "--watch"],
                command: "node",
                id: "stdio-calendar",
                name: "Calendar",
                transport: "stdio",
              },
              {
                auth: { token: "secret", type: "bearer" },
                id: "http-docs",
                name: "Docs",
                transport: "http",
                url: "https://mcp.example.test",
              },
              {
                id: "broken-server",
                name: "Broken",
                transport: "stdio",
              },
            ],
            metadata: {
              ui: {
                lastUpdatedAt: 44,
                queue: [{ createdAt: 44, id: "q_1", kind: "user", text: "queued follow-up" }],
                queueLength: 99,
                status: "running",
              },
            },
            modeId: "plan",
            runtimeConfig: {
              adapter: "openai-responses",
              env: {
                OPENAI_API_KEY: "sk-test",
                TRIMMED: "  kept  ",
                UNUSED_EMPTY: "",
              },
              modelId: "gpt-5",
            },
            toolApprovalMode: "strict",
            version: 1,
          },
          title: "Legacy imported session",
        },
        sessionId: "session-alpha",
        updatedAt: 20,
      }, null, 2)}\n`,
      "utf8",
    );

    const payloads: LegacySessionPayload[] = [];

    const marker = await importLegacySessions({
      activeStateDir,
      stateRoots: [legacyStateDir],
      fallbackCwd: "/repo/fallback",
      fallbackRuntimeConfig: {
        adapter: "openai",
        modelId: "fallback-model",
        provider: "openai",
      },
      hasRuntimeSnapshot: async () => false,
      materializeSession: async (payload) => {
        payloads.push(payload);
      },
    });

    expect(payloads).toHaveLength(1);
    expect(payloads[0]).toMatchObject({
      cwd: "/repo/legacy",
      modeId: "plan",
      sessionId: "session-alpha",
      toolApprovalMode: "strict",
    });
    expect(payloads[0].runtimeConfig).toMatchObject({
      adapter: "openai-responses",
      modelId: "gpt-5",
    });
    expect(payloads[0].runtimeConfig.env).toEqual({
      OPENAI_API_KEY: "sk-test",
      TRIMMED: "kept",
    });
    expect(payloads[0].mcpServers).toEqual([
      {
        args: ["server.js", "--watch"],
        command: "node",
        id: "stdio-calendar",
        name: "Calendar",
        transport: "stdio",
      },
      {
        auth: { token: "secret", type: "bearer" },
        id: "http-docs",
        name: "Docs",
        transport: "http",
        url: "https://mcp.example.test",
      },
    ]);
    expect(payloads[0].metadata.ui).toMatchObject({
      lastUpdatedAt: 44,
      queueLength: 1,
      status: "running",
    });

    expect(marker.importedSessions).toEqual([
      {
        sessionId: "session-alpha",
        sourceKind: "session-json",
        sourcePath: path.join(legacySessionDir, "session.json"),
        targetPath: runtimeSnapshotPath(activeStateDir, "session-alpha"),
      },
    ]);

    const writtenMarker = JSON.parse(
      readFileSync(
        path.join(activeStateDir, "migrations", "runtime-import-v2.json"),
        "utf8",
      ),
    );
    expect(writtenMarker.importedSessions).toHaveLength(1);
  });

  it("falls back to the provided runtime adapter when the legacy snapshot stores garbage", async () => {
    const root = mkdtempSync(path.join(tmpdir(), "best-agent-runtime-import-fallback-"));
    tempRoots.push(root);

    const activeStateDir = path.join(root, "active-state");
    const legacyStateDir = path.join(root, "legacy-state");
    const legacySessionDir = path.join(legacyStateDir, "session-beta");
    await mkdir(legacySessionDir, { recursive: true });

    await writeFile(
      path.join(legacySessionDir, "session.json"),
      `${JSON.stringify({
        context: { messages: [] },
        metadata: {
          dimAgentRuntime: {
            runtimeConfig: {
              adapter: "definitely-not-real",
              modelId: "legacy-model",
            },
            version: 1,
          },
        },
        sessionId: "session-beta",
      }, null, 2)}\n`,
      "utf8",
    );

    let importedAdapter: unknown;
    await importLegacySessions({
      activeStateDir,
      stateRoots: [legacyStateDir],
      fallbackCwd: "/repo/fallback",
      fallbackRuntimeConfig: {
        adapter: "anthropic",
        modelId: "fallback-model",
        provider: "anthropic",
      },
      hasRuntimeSnapshot: async () => false,
      materializeSession: async (payload) => {
        importedAdapter = payload.runtimeConfig.adapter;
      },
    });

    expect(importedAdapter).toBe("anthropic");
  });

  it("patches imported snapshots with the normalized runtime envelope", () => {
    const patched = patchImportedSnapshot(
      {
        createdAt: 10,
        cwd: "/repo/new",
        messages: [],
        metadata: {
          [RUNTIME_SESSION_METADATA_KEY]: {
            lastStopReason: "old",
            title: "Old title",
          },
          preserved: true,
        },
        schemaVersion: 3,
        sessionId: "session-alpha",
        updatedAt: 20,
      } as never,
      {
        createdAt: 1,
        cwd: "/repo/imported",
        mcpServers: [
          {
            args: ["server.js"],
            command: "node",
            id: "stdio-calendar",
            name: "Calendar",
            transport: "stdio",
          },
        ],
        messages: [],
        metadata: {
          imported: true,
          ui: {
            queue: [{ createdAt: 1, id: "q_1", kind: "user", text: "queued" }],
            queueLength: 1,
            status: "running",
          },
        },
        modeId: "plan",
        runtimeConfig: {
          adapter: "openai",
          modelId: "gpt-5",
          provider: "openai",
        },
        sessionId: "session-alpha",
        toolApprovalMode: "normal",
        updatedAt: 2,
      },
    );

    expect(patched.cwd).toBe("/repo/imported");
    expect((patched.metadata as Record<string, unknown>).ui).toMatchObject({
      queueLength: 1,
      status: "running",
    });
    expect(
      ((patched.metadata as Record<string, unknown>)[RUNTIME_SESSION_METADATA_KEY] as Record<string, unknown>).mcpServers,
    ).toEqual([
      {
        args: ["server.js"],
        command: "node",
        id: "stdio-calendar",
        name: "Calendar",
        transport: "stdio",
      },
    ]);
  });
});
