import { OpencodeBackendDescriptor } from "./descriptor";

jest.mock("@/logger", () => ({
  logInfo: jest.fn(),
  logWarn: jest.fn(),
  logError: jest.fn(),
}));

const registeredProviderIds = new Set<string>();
const registeredModelKeys = new Set<string>();

jest.mock("@/modelManagement", () => ({
  ProviderRegistry: {
    getInstance: () => ({
      get: (id: string) => (registeredProviderIds.has(id) ? { id } : undefined),
    }),
  },
  ModelRegistry: {
    getInstance: () => ({
      get: (providerId: string, modelId: string) =>
        registeredModelKeys.has(`${providerId}|${modelId}`) ? { providerId, modelId } : undefined,
    }),
  },
}));

beforeEach(() => {
  registeredProviderIds.clear();
  registeredModelKeys.clear();
});

describe("OpencodeBackendDescriptor.wire.decode", () => {
  const decode = OpencodeBackendDescriptor.wire.decode;

  it("parses 2-segment ids as bare/default with provider mapped to Copilot", () => {
    expect(decode("anthropic/claude-sonnet-4-5")).toEqual({
      selection: { baseModelId: "anthropic/claude-sonnet-4-5", effort: null },
      provider: "anthropic",
    });
  });

  it("parses 3-segment ids as variants when the suffix is a known effort", () => {
    expect(decode("anthropic/claude-sonnet-4-5/medium")).toEqual({
      selection: { baseModelId: "anthropic/claude-sonnet-4-5", effort: "medium" },
      provider: "anthropic",
    });
    expect(decode("openai/gpt-5/minimal")).toEqual({
      selection: { baseModelId: "openai/gpt-5", effort: "minimal" },
      provider: "openai",
    });
  });

  it("recognizes opencode's full effort vocabulary (none/minimal/low/medium/high/xhigh/max)", () => {
    // Opencode advertises Anthropic models with `/max` and `/xhigh` and
    // OpenRouter reasoning models with `/none`. Each must collapse onto
    // its bare base.
    for (const effort of ["none", "minimal", "low", "medium", "high", "xhigh", "max"]) {
      expect(decode(`anthropic/claude-opus-4-7/${effort}`)).toEqual({
        selection: { baseModelId: "anthropic/claude-opus-4-7", effort },
        provider: "anthropic",
      });
    }
  });

  it("returns no-effort representation for 3-segment ids whose suffix isn't a known effort", () => {
    // OpenRouter-style 3-segment ids without an effort suffix — the
    // trailing segment is part of the model name. The whole id is the
    // baseModelId; provider is still attributed from the leading segment.
    expect(decode("openrouter/anthropic/claude-sonnet-4-5")).toEqual({
      selection: { baseModelId: "openrouter/anthropic/claude-sonnet-4-5", effort: null },
      provider: "openrouterai",
    });
    expect(decode("openrouter/anthropic/claude-3.5-haiku")).toEqual({
      selection: { baseModelId: "openrouter/anthropic/claude-3.5-haiku", effort: null },
      provider: "openrouterai",
    });
  });

  it("parses 4-segment umbrella ids as variants when the last segment is a known effort", () => {
    // OpenRouter wraps native ids under `openrouter/`, so its variants
    // are 4-segment: `openrouter/<sub>/<model>/<effort>`. Without this
    // case the picker would render seven duplicate rows per OpenRouter
    // reasoning model.
    expect(decode("openrouter/anthropic/claude-sonnet-4.5/high")).toEqual({
      selection: { baseModelId: "openrouter/anthropic/claude-sonnet-4.5", effort: "high" },
      provider: "openrouterai",
    });
    expect(decode("openrouter/anthropic/claude-sonnet-4.5/none")).toEqual({
      selection: { baseModelId: "openrouter/anthropic/claude-sonnet-4.5", effort: "none" },
      provider: "openrouterai",
    });
    expect(decode("openrouter/openai/gpt-5/xhigh")).toEqual({
      selection: { baseModelId: "openrouter/openai/gpt-5", effort: "xhigh" },
      provider: "openrouterai",
    });
    // OpenRouter route variants like `:exacto` live inside the model
    // segment — the effort suffix still attaches at the trailing slash.
    expect(decode("openrouter/openai/gpt-oss-120b:exacto/none")).toEqual({
      selection: { baseModelId: "openrouter/openai/gpt-oss-120b:exacto", effort: "none" },
      provider: "openrouterai",
    });
  });

  it("returns no-effort representation for unparseable shapes (1 segment or unknown trailing segment)", () => {
    // 1-segment ids have no provider segment to attribute.
    expect(decode("just-a-name")).toEqual({
      selection: { baseModelId: "just-a-name", effort: null },
      provider: null,
    });
    // 4+ segment ids whose trailing segment isn't a known effort fall
    // through to a no-effort representation. The leading segment still
    // attributes a provider when it maps.
    expect(decode("anthropic/foo/bar/baz")).toEqual({
      selection: { baseModelId: "anthropic/foo/bar/baz", effort: null },
      provider: "anthropic",
    });
    expect(decode("a/b/c/d")).toEqual({
      selection: { baseModelId: "a/b/c/d", effort: null },
      provider: null,
    });
  });
});

describe("OpencodeBackendDescriptor.wire.encode", () => {
  const encode = OpencodeBackendDescriptor.wire.encode;

  it("returns the bare baseModelId when effort is null", () => {
    expect(encode({ baseModelId: "anthropic/claude-sonnet-4-5", effort: null })).toBe(
      "anthropic/claude-sonnet-4-5"
    );
  });

  it("appends the variant when effort is set", () => {
    expect(encode({ baseModelId: "anthropic/claude-sonnet-4-5", effort: "high" })).toBe(
      "anthropic/claude-sonnet-4-5/high"
    );
  });

  it("round-trips via wire.decode", () => {
    const ids = [
      "anthropic/claude-sonnet-4-5",
      "anthropic/claude-sonnet-4-5/low",
      "openai/gpt-5/high",
      "anthropic/claude-opus-4-7/max",
      "openrouter/anthropic/claude-sonnet-4.5",
      "openrouter/anthropic/claude-sonnet-4.5/none",
      "openrouter/anthropic/claude-sonnet-4.5/high",
    ];
    for (const id of ids) {
      const decoded = OpencodeBackendDescriptor.wire.decode(id);
      expect(encode(decoded.selection)).toBe(id);
    }
  });
});

describe("OpencodeBackendDescriptor.isModelEnabledByDefault", () => {
  const fn = OpencodeBackendDescriptor.isModelEnabledByDefault!;

  it("matches 'Big Pickle' in name", () => {
    expect(fn({ modelId: "bigpickle/foo", name: "Big Pickle" })).toBe(true);
    expect(fn({ modelId: "bigpickle/foo", name: "BIG PICKLE" })).toBe(true);
    expect(fn({ modelId: "bigpickle/foo", name: "big-pickle" })).toBe(true);
  });

  it("matches 'big-pickle' in modelId", () => {
    expect(fn({ modelId: "bigpickle/big-pickle", name: "Some Display" })).toBe(true);
    expect(fn({ modelId: "bigpickle/big_pickle", name: "Some Display" })).toBe(true);
  });

  it("returns false for non-BYOK, non-Big-Pickle models", () => {
    // No providers registered in BYOK → unrelated models stay opt-in.
    expect(fn({ modelId: "anthropic/claude-sonnet-4-5", name: "Claude Sonnet 4.5" })).toBe(false);
    expect(fn({ modelId: "openai/gpt-5", name: "GPT-5" })).toBe(false);
  });

  it("default-enables models whose (providerId, modelId) is in ModelRegistry", () => {
    registeredProviderIds.add("openrouter");
    registeredProviderIds.add("custom:abc-uuid");
    registeredModelKeys.add("openrouter|anthropic/claude-sonnet-4.5");
    registeredModelKeys.add("custom:abc-uuid|llama-3.3");
    expect(
      fn({ modelId: "openrouter/anthropic/claude-sonnet-4.5", name: "Claude Sonnet 4.5" })
    ).toBe(true);
    expect(fn({ modelId: "custom:abc-uuid/llama-3.3", name: "Llama 3.3" })).toBe(true);
    // Other providers without a BYOK entry remain opt-in.
    expect(fn({ modelId: "groq/llama-3.1-70b", name: "Llama 3.1 70B" })).toBe(false);
  });

  it("does NOT default-enable models from a BYOK provider when the modelId isn't in ModelRegistry", () => {
    // Real scenario: user registered OpenRouter but only picked 2 models.
    // OpenCode's bundled snapshot reports the other ~50 openrouter rows.
    // Those must stay opt-in even though the provider is registered.
    registeredProviderIds.add("openrouter");
    registeredModelKeys.add("openrouter|moonshotai/kimi-k2-thinking");
    expect(
      fn({
        modelId: "openrouter/moonshotai/kimi-k2-thinking",
        name: "Kimi K2 Thinking",
      })
    ).toBe(true);
    expect(fn({ modelId: "openrouter/google/gemini-2.5-pro", name: "Gemini 2.5 Pro" })).toBe(false);
  });

  it("does not default-enable copilot-plus rows (system provider, not BYOK)", () => {
    // Plus rows flow through the OpenCode picker but are NOT BYOK; they
    // should stay subject to the Big Pickle regex / explicit user toggle.
    expect(fn({ modelId: "copilot-plus/copilot-plus-flash", name: "Plus Flash" })).toBe(false);
  });

  it("does not default-enable single-segment modelIds (no provider segment to look up)", () => {
    registeredProviderIds.add("anthropic");
    registeredModelKeys.add("anthropic|"); // even if registry happened to contain it, no slash → no match
    expect(fn({ modelId: "anthropic", name: "Anthropic" })).toBe(false);
  });
});
