import type { InstallState } from "@/agentMode/session/types";
import type { CopilotSettings } from "@/settings/model";
import { resolveClaudeBinary } from "./claudeBinaryResolver";
import { claudeCompatibilityStore } from "./claudeCompatibilityStore";
import {
  getClaudeInstallState,
  refreshClaudeInstallState,
  subscribeClaudeInstallState,
} from "./descriptor";

jest.mock("./claudeBinaryResolver", () => ({
  claudeBinarySearchDirs: jest.fn(() => []),
  resolveClaudeBinary: jest.fn(),
}));

jest.mock("./claudeCompatibilityStore", () => ({
  claudeCompatibilityStore: {
    get: jest.fn(),
    refresh: jest.fn(),
    subscribe: jest.fn(),
  },
}));

const mockResolveClaudeBinary = resolveClaudeBinary as jest.MockedFunction<
  typeof resolveClaudeBinary
>;
const mockGetCompatibility = claudeCompatibilityStore.get as jest.MockedFunction<
  typeof claudeCompatibilityStore.get
>;
const mockRefreshCompatibility = claudeCompatibilityStore.refresh as jest.MockedFunction<
  typeof claudeCompatibilityStore.refresh
>;
const mockSubscribeCompatibility = claudeCompatibilityStore.subscribe as jest.MockedFunction<
  typeof claudeCompatibilityStore.subscribe
>;

function settingsWithClaudeRuntime(options: {
  path?: string;
  envOverrides?: Record<string, string>;
}): CopilotSettings {
  return {
    agentMode: {
      claudeCli: options.path ? { path: options.path } : undefined,
      backends: {
        claude: {
          envOverrides: options.envOverrides,
        },
      },
    },
  } as unknown as CopilotSettings;
}

describe("claude descriptor", () => {
  beforeEach(() => {
    jest.resetAllMocks();
  });

  describe("getClaudeInstallState()", () => {
    it("returns absent without consulting compatibility state when no executable resolves", () => {
      mockResolveClaudeBinary.mockReturnValue(null);

      const first = getClaudeInstallState(settingsWithClaudeRuntime({}));

      expect(first).toEqual({ kind: "absent" });
      expect(getClaudeInstallState(settingsWithClaudeRuntime({}))).toBe(first);
      expect(mockGetCompatibility).not.toHaveBeenCalled();
    });

    it("reads compatibility state using the custom executable and sorted environment identity", () => {
      const readyState: InstallState = { kind: "ready", source: "custom" };
      mockResolveClaudeBinary.mockReturnValue("/custom/bin/claude");
      mockGetCompatibility.mockReturnValue(readyState);

      const result = getClaudeInstallState(
        settingsWithClaudeRuntime({
          path: "/custom/bin/claude",
          envOverrides: { ZED: "last", ALPHA: "first" },
        })
      );

      expect(result).toBe(readyState);
      expect(mockGetCompatibility).toHaveBeenCalledWith({
        cacheKey: 'custom\u0000/custom/bin/claude\u0000[["ALPHA","first"],["ZED","last"]]',
        path: "/custom/bin/claude",
        source: "custom",
        env: expect.objectContaining({ ALPHA: "first", ZED: "last" }),
      });
    });
  });

  describe("refreshClaudeInstallState()", () => {
    it("does not refresh compatibility state when no executable resolves", async () => {
      mockResolveClaudeBinary.mockReturnValue(null);

      await expect(refreshClaudeInstallState(settingsWithClaudeRuntime({}), true)).resolves.toEqual(
        {
          kind: "absent",
        }
      );
      expect(mockRefreshCompatibility).not.toHaveBeenCalled();
    });

    it("forces a refresh for the managed executable and returns its new state", async () => {
      const readyState: InstallState = { kind: "ready", source: "managed" };
      mockResolveClaudeBinary.mockReturnValue("/managed/bin/claude");
      mockRefreshCompatibility.mockResolvedValue(readyState);

      const result = await refreshClaudeInstallState(settingsWithClaudeRuntime({}), true);

      expect(result).toBe(readyState);
      expect(mockRefreshCompatibility).toHaveBeenCalledWith(
        {
          cacheKey: "managed\u0000/managed/bin/claude\u0000[]",
          path: "/managed/bin/claude",
          source: "managed",
          env: process.env,
        },
        { force: true }
      );
    });
  });

  describe("subscribeClaudeInstallState()", () => {
    it("subscribes to compatibility changes and returns the matching unsubscribe function", () => {
      const listener = jest.fn();
      const unsubscribe = jest.fn();
      mockSubscribeCompatibility.mockReturnValue(unsubscribe);

      expect(subscribeClaudeInstallState(listener)).toBe(unsubscribe);
      expect(mockSubscribeCompatibility).toHaveBeenCalledWith(listener);
    });
  });
});
