import path from "path"
import { describe, expect } from "bun:test"
import { Money } from "@opencode-ai/schema/money"
import { Effect, Layer } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Integration } from "@opencode-ai/core/integration"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { EventV2 } from "@opencode-ai/core/event"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Location } from "@opencode-ai/core/location"
import { ModelV2 } from "@opencode-ai/core/model"
import { ModelsDev } from "@opencode-ai/core/models-dev"
import { ModelsDevPlugin } from "@opencode-ai/core/plugin/models-dev"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { location } from "../fixture/location"
import { testEffect } from "../lib/effect"
import { catalogHost, host, integrationHost } from "./host"

const locationLayer = Layer.succeed(
  Location.Service,
  Location.Service.of(location({ directory: AbsolutePath.make(import.meta.dir) })),
)
const layer = AppNodeBuilder.build(LayerNode.group([Catalog.node, Integration.node, EventV2.node]), [
  [Location.node, locationLayer],
])
const it = testEffect(layer)

describe("ModelsDevPlugin", () => {
  it.effect("projects normalized models.dev snapshots into the catalog", () =>
    Effect.gen(function* () {
      const integrations = yield* Integration.Service
      const catalog = yield* Catalog.Service
      const providerID = ProviderV2.ID.make("acme")
      const modelID = ModelV2.ID.make("gpt-5.4")
      const models = ModelsDev.Service.of({
        get: () =>
          Effect.succeed([
            {
              info: {
                id: providerID,
                name: "Acme",
                package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
                settings: { baseURL: "https://api.acme.test/v1" },
              },
              environment: [],
              models: [
                {
                  id: modelID,
                  modelID,
                  providerID,
                  name: "GPT-5.4",
                  family: ModelV2.Family.make("gpt"),
                  capabilities: { tools: true, input: [], output: [] },
                  variants: [],
                  time: { released: Date.parse("2026-01-01") },
                  cost: [
                    {
                      input: Money.USDPerMillionTokens.make(2.5),
                      output: Money.USDPerMillionTokens.make(15),
                      cache: {
                        read: Money.USDPerMillionTokens.zero,
                        write: Money.USDPerMillionTokens.zero,
                      },
                    },
                    {
                      tier: { type: "context", size: 272_000 },
                      input: Money.USDPerMillionTokens.make(3),
                      output: Money.USDPerMillionTokens.make(18),
                      cache: {
                        read: Money.USDPerMillionTokens.make(0.25),
                        write: Money.USDPerMillionTokens.zero,
                      },
                    },
                    {
                      tier: { type: "context", size: 200_000 },
                      input: Money.USDPerMillionTokens.make(5),
                      output: Money.USDPerMillionTokens.make(22.5),
                      cache: {
                        read: Money.USDPerMillionTokens.make(0.5),
                        write: Money.USDPerMillionTokens.zero,
                      },
                    },
                  ],
                  status: "active",
                  enabled: true,
                  limit: { context: 1_050_000, input: 922_000, output: 128_000 },
                },
                {
                  id: ModelV2.ID.make("gpt-5.4-fast"),
                  modelID,
                  providerID,
                  name: "GPT-5.4 Fast",
                  family: ModelV2.Family.make("gpt"),
                  package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
                  settings: { baseURL: "https://api.acme.test/v1" },
                  headers: { "x-mode": "fast" },
                  body: { service_tier: "priority" },
                  capabilities: { tools: true, input: [], output: [] },
                  variants: [],
                  time: { released: Date.parse("2026-01-01") },
                  cost: [
                    {
                      input: Money.USDPerMillionTokens.make(5),
                      output: Money.USDPerMillionTokens.make(30),
                      cache: {
                        read: Money.USDPerMillionTokens.make(0.5),
                        write: Money.USDPerMillionTokens.zero,
                      },
                    },
                    {
                      tier: { type: "context", size: 272_000 },
                      input: Money.USDPerMillionTokens.make(3),
                      output: Money.USDPerMillionTokens.make(18),
                      cache: {
                        read: Money.USDPerMillionTokens.make(0.25),
                        write: Money.USDPerMillionTokens.zero,
                      },
                    },
                    {
                      tier: { type: "context", size: 200_000 },
                      input: Money.USDPerMillionTokens.make(5),
                      output: Money.USDPerMillionTokens.make(22.5),
                      cache: {
                        read: Money.USDPerMillionTokens.make(0.5),
                        write: Money.USDPerMillionTokens.zero,
                      },
                    },
                  ],
                  status: "active",
                  enabled: true,
                  limit: { context: 1_050_000, input: 922_000, output: 128_000 },
                },
              ],
            },
          ] satisfies readonly ModelsDev.Snapshot[]),
        refresh: () => Effect.void,
      })

      yield* ModelsDevPlugin.effect(
        host({
          catalog: catalogHost(catalog),
          integration: integrationHost(integrations),
        }),
      ).pipe(Effect.provideService(ModelsDev.Service, models))

      const base = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4"))
      const fast = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4-fast"))

      expect(base?.variants).toEqual([])
      expect(base?.body).toBeUndefined()
      expect(fast).toMatchObject({
        id: "gpt-5.4-fast",
        modelID: "gpt-5.4",
        providerID: "acme",
        name: "GPT-5.4 Fast",
        package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
        settings: { baseURL: "https://api.acme.test/v1" },
        headers: { "x-mode": "fast" },
        body: { service_tier: "priority" },
        variants: [],
      })
      expect(fast?.cost).toEqual([
        {
          input: Money.USDPerMillionTokens.make(5),
          output: Money.USDPerMillionTokens.make(30),
          cache: {
            read: Money.USDPerMillionTokens.make(0.5),
            write: Money.USDPerMillionTokens.zero,
          },
        },
        {
          tier: { type: "context", size: 272_000 },
          input: Money.USDPerMillionTokens.make(3),
          output: Money.USDPerMillionTokens.make(18),
          cache: {
            read: Money.USDPerMillionTokens.make(0.25),
            write: Money.USDPerMillionTokens.zero,
          },
        },
        {
          tier: { type: "context", size: 200_000 },
          input: Money.USDPerMillionTokens.make(5),
          output: Money.USDPerMillionTokens.make(22.5),
          cache: {
            read: Money.USDPerMillionTokens.make(0.5),
            write: Money.USDPerMillionTokens.zero,
          },
        },
      ])
    }),
  )

  it.effect("registers key methods for providers with environment variables", () =>
    Effect.acquireUseRelease(
      Effect.sync(() => {
        const previous = {
          path: Flag.OPENCODE_MODELS_PATH,
          disabled: Flag.OPENCODE_DISABLE_MODELS_FETCH,
        }
        Flag.OPENCODE_MODELS_PATH = path.join(import.meta.dir, "fixtures", "models-dev.json")
        Flag.OPENCODE_DISABLE_MODELS_FETCH = true
        return previous
      }),
      () =>
        Effect.gen(function* () {
          const integrations = yield* Integration.Service
          const catalog = yield* Catalog.Service
          yield* ModelsDevPlugin.effect(
            host({
              catalog: catalogHost(catalog),
              integration: integrationHost(integrations),
            }),
          )
          expect(yield* integrations.list()).toEqual([
            Integration.Info.make({
              id: Integration.ID.make("acme"),
              name: "Acme",
              methods: [
                { type: "key" },
                {
                  type: "env",
                  names: ["ACME_API_KEY"],
                },
              ],
              connections: [],
            }),
          ])
        }).pipe(Effect.provide(AppNodeBuilder.build(ModelsDev.node))),
      (previous) =>
        Effect.sync(() => {
          Flag.OPENCODE_MODELS_PATH = previous.path
          Flag.OPENCODE_DISABLE_MODELS_FETCH = previous.disabled
        }),
    ),
  )

  it.effect("converts reasoning options into settings variants", () =>
    Effect.acquireUseRelease(
      Effect.sync(() => {
        const previous = {
          path: Flag.OPENCODE_MODELS_PATH,
          disabled: Flag.OPENCODE_DISABLE_MODELS_FETCH,
        }
        Flag.OPENCODE_MODELS_PATH = path.join(import.meta.dir, "fixtures", "models-dev-reasoning.json")
        Flag.OPENCODE_DISABLE_MODELS_FETCH = true
        return previous
      }),
      () =>
        Effect.gen(function* () {
          const catalog = yield* Catalog.Service
          const integrations = yield* Integration.Service
          yield* ModelsDevPlugin.effect(
            host({
              catalog: catalogHost(catalog),
              integration: integrationHost(integrations),
            }),
          )

          const model = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning"))
          expect(model?.variants?.map((variant) => variant.id)).toEqual([
            ModelV2.VariantID.make("low"),
            ModelV2.VariantID.make("high"),
          ])
          expect(model?.variants).toContainEqual({
            id: ModelV2.VariantID.make("low"),
            settings: {
              reasoningEffort: "low",
              reasoningSummary: "auto",
              include: ["reasoning.encrypted_content"],
            },
          })
          expect(model?.variants).toContainEqual({
            id: ModelV2.VariantID.make("high"),
            settings: {
              reasoningEffort: "high",
              reasoningSummary: "auto",
              include: ["reasoning.encrypted_content"],
            },
          })

          const mode = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning-high"))
          expect(mode).toMatchObject({
            id: "gpt-reasoning-high",
            name: "GPT Reasoning High",
            headers: { "x-mode": "high" },
            body: { service_tier: "priority" },
          })
          expect(mode?.variants?.map((variant) => variant.id)).toEqual([
            ModelV2.VariantID.make("low"),
            ModelV2.VariantID.make("high"),
          ])

          const pro = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning-pro"))
          expect(pro).toMatchObject({
            id: "gpt-reasoning-pro",
            body: { reasoning: { mode: "pro" } },
          })

          const budgetModel = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-budget"))
          expect(budgetModel?.variants).toContainEqual({
            id: ModelV2.VariantID.make("high"),
            settings: { thinking: { type: "enabled", budgetTokens: 16000 } },
          })
          expect(budgetModel?.variants).toContainEqual({
            id: ModelV2.VariantID.make("max"),
            settings: { thinking: { type: "enabled", budgetTokens: 31999 } },
          })

          const anthropicEffortModel = yield* catalog.model.get(
            ProviderV2.ID.anthropic,
            ModelV2.ID.make("claude-opus-4.7"),
          )
          expect(anthropicEffortModel?.variants).toEqual([
            { id: ModelV2.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
            {
              id: ModelV2.VariantID.make("low"),
              settings: { thinking: { type: "adaptive", display: "summarized" }, effort: "low" },
            },
          ])

          const anthropicToggleModel = yield* catalog.model.get(
            ProviderV2.ID.anthropic,
            ModelV2.ID.make("claude-toggle"),
          )
          expect(anthropicToggleModel?.variants).toEqual([
            { id: ModelV2.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
            {
              id: ModelV2.VariantID.make("thinking"),
              settings: { thinking: { type: "adaptive", display: "summarized" } },
            },
          ])

          const opus45 = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-opus-4-5"))
          expect(opus45?.variants).toEqual([
            { id: ModelV2.VariantID.make("low"), settings: { effort: "low" } },
            { id: ModelV2.VariantID.make("high"), settings: { effort: "high" } },
          ])

          const grok = yield* catalog.model.get(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4.5"))
          expect(grok?.variants).toEqual(
            ["low", "medium", "high"].map((id) => ({
              id: ModelV2.VariantID.make(id),
              settings: { reasoningEffort: id },
            })),
          )

          const minimax = yield* catalog.model.get(ProviderV2.ID.make("opencode-go"), ModelV2.ID.make("minimax-m3"))
          expect(minimax?.variants).toEqual([
            { id: ModelV2.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
            {
              id: ModelV2.VariantID.make("thinking"),
              settings: { thinking: { type: "adaptive", display: "summarized" } },
            },
          ])

          const toggle = yield* catalog.model.get(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("toggle-only"))
          expect(toggle?.variants).toEqual([
            { id: ModelV2.VariantID.make("none"), settings: { enableThinking: false } },
            { id: ModelV2.VariantID.make("thinking"), settings: { enableThinking: true } },
          ])

          const combined = yield* catalog.model.get(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("toggle-budget"))
          expect(combined?.variants).toEqual([
            { id: ModelV2.VariantID.make("none"), settings: { enableThinking: false } },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { enableThinking: true, thinkingBudget: 8000 },
            },
            {
              id: ModelV2.VariantID.make("max"),
              settings: { enableThinking: true, thinkingBudget: 16000 },
            },
          ])

          const gateway = yield* catalog.model.get(ProviderV2.ID.make("vercel"), ModelV2.ID.make("alibaba/qwen-toggle"))
          expect(gateway?.variants).toEqual([
            { id: ModelV2.VariantID.make("none"), settings: { enableThinking: false } },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { enableThinking: true, thinkingBudget: 8000 },
            },
            {
              id: ModelV2.VariantID.make("max"),
              settings: { enableThinking: true, thinkingBudget: 16000 },
            },
          ])

          const gatewayNova = yield* catalog.model.get(
            ProviderV2.ID.make("vercel"),
            ModelV2.ID.make("amazon/nova-2-lite"),
          )
          expect(gatewayNova?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: { additionalModelRequestFields: { reasoningConfig: { type: "disabled" } } },
            },
            {
              id: ModelV2.VariantID.make("low"),
              settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "low" } },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "high" } },
            },
          ])

          const gatewayFallback = yield* catalog.model.get(
            ProviderV2.ID.make("vercel"),
            ModelV2.ID.make("deepseek/deepseek-toggle"),
          )
          expect(gatewayFallback?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: { reasoning: { enabled: false } },
            },
            {
              id: ModelV2.VariantID.make("low"),
              settings: { reasoningEffort: "low" },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { reasoningEffort: "high" },
            },
          ])

          const openrouter = yield* catalog.model.get(
            ProviderV2.ID.make("openrouter"),
            ModelV2.ID.make("openrouter-toggle"),
          )
          expect(openrouter?.variants).toEqual([
            { id: ModelV2.VariantID.make("none"), settings: { reasoning: { enabled: false } } },
            { id: ModelV2.VariantID.make("thinking"), settings: { reasoning: { enabled: true } } },
          ])

          const google = yield* catalog.model.get(ProviderV2.ID.make("google"), ModelV2.ID.make("gemini-2.5-flash"))
          expect(google?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: { thinkingConfig: { includeThoughts: false, thinkingBudget: 0 } },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 8000 } },
            },
            {
              id: ModelV2.VariantID.make("max"),
              settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
            },
          ])

          const vertex = yield* catalog.model.get(
            ProviderV2.ID.make("google-vertex"),
            ModelV2.ID.make("gemini-2.5-flash-lite"),
          )
          expect(vertex?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: { thinkingConfig: { includeThoughts: false, thinkingBudget: 0 } },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 8000 } },
            },
            {
              id: ModelV2.VariantID.make("max"),
              settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
            },
          ])

          const bedrock = yield* catalog.model.get(
            ProviderV2.ID.make("amazon-bedrock"),
            ModelV2.ID.make("amazon.nova-2-lite-v1:0"),
          )
          expect(bedrock?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: { additionalModelRequestFields: { reasoningConfig: { type: "disabled" } } },
            },
            {
              id: ModelV2.VariantID.make("low"),
              settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "low" } },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "high" } },
            },
          ])

          const sapGemini = yield* catalog.model.get(
            ProviderV2.ID.make("sap-ai-core"),
            ModelV2.ID.make("gemini-2.5-flash"),
          )
          expect(sapGemini?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: { modelParams: { thinkingConfig: { includeThoughts: false, thinkingBudget: 0 } } },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: 8000 } } },
            },
            {
              id: ModelV2.VariantID.make("max"),
              settings: { modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
            },
          ])

          const sapNova = yield* catalog.model.get(
            ProviderV2.ID.make("sap-ai-core"),
            ModelV2.ID.make("amazon--nova-lite"),
          )
          expect(sapNova?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: {
                modelParams: { additionalModelRequestFields: { thinking: { type: "disabled" } } },
              },
            },
            {
              id: ModelV2.VariantID.make("low"),
              settings: {
                modelParams: { additionalModelRequestFields: { output_config: { effort: "low" } } },
              },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: {
                modelParams: { additionalModelRequestFields: { output_config: { effort: "high" } } },
              },
            },
          ])

          const sapCohere = yield* catalog.model.get(
            ProviderV2.ID.make("sap-ai-core"),
            ModelV2.ID.make("cohere--command-a-reasoning"),
          )
          expect(sapCohere?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("none"),
              settings: { modelParams: { thinking: { type: "disabled" } } },
            },
            {
              id: ModelV2.VariantID.make("low"),
              settings: { modelParams: { reasoning_effort: "low" } },
            },
            {
              id: ModelV2.VariantID.make("high"),
              settings: { modelParams: { reasoning_effort: "high" } },
            },
          ])

          const sapAnthropicEffort = yield* catalog.model.get(
            ProviderV2.ID.make("sap-ai-core"),
            ModelV2.ID.make("anthropic--claude-4.7-opus"),
          )
          expect(sapAnthropicEffort?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("low"),
              settings: {
                modelParams: {
                  additionalModelRequestFields: {
                    thinking: { type: "adaptive", display: "summarized" },
                    output_config: { effort: "low" },
                  },
                },
              },
            },
          ])

          const sapAnthropicBudget = yield* catalog.model.get(
            ProviderV2.ID.make("sap-ai-core"),
            ModelV2.ID.make("anthropic--claude-4-sonnet"),
          )
          expect(sapAnthropicBudget?.variants).toEqual([
            {
              id: ModelV2.VariantID.make("high"),
              settings: {
                modelParams: {
                  additionalModelRequestFields: { thinking: { type: "enabled", budget_tokens: 8000 } },
                },
              },
            },
            {
              id: ModelV2.VariantID.make("max"),
              settings: {
                modelParams: {
                  additionalModelRequestFields: { thinking: { type: "enabled", budget_tokens: 16000 } },
                },
              },
            },
          ])
        }).pipe(Effect.provide(AppNodeBuilder.build(ModelsDev.node))),
      (previous) =>
        Effect.sync(() => {
          Flag.OPENCODE_MODELS_PATH = previous.path
          Flag.OPENCODE_DISABLE_MODELS_FETCH = previous.disabled
        }),
    ),
  )
})
