import { test, expect, describe } from "bun:test"
import { Schema } from "effect"
import { Provider } from "@/provider/provider"
import { ProviderID, ModelID } from "../../src/provider/schema"
import { LOCAL_PROVIDERS, CUSTOM_PROVIDERS, CLOUD_PROVIDERS } from "@/provider/custom-loaders"
import { ProviderCategory, Info } from "@/provider/provider-schemas"

const local = LOCAL_PROVIDERS as readonly string[]
const custom = CUSTOM_PROVIDERS as readonly string[]
const cloud = CLOUD_PROVIDERS as readonly string[]

describe("provider category constants", () => {
  test("LOCAL_PROVIDERS contains expected local providers", () => {
    expect(local).toContain("ollama")
    expect(local).toContain("lmstudio")
    expect(local).toContain("vllm")
    expect(local).toContain("text-generation-webui")
    expect(local).toContain("llama.cpp")
  })

  test("CUSTOM_PROVIDERS contains expected custom providers", () => {
    expect(custom).toContain("openrouter")
    expect(custom).toContain("llmgateway")
    expect(custom).toContain("nvidia")
    expect(custom).toContain("vercel")
    expect(custom).toContain("zenmux")
    expect(custom).toContain("cerebras")
  })

  test("CLOUD_PROVIDERS contains expected cloud providers", () => {
    expect(cloud).toContain("anthropic")
    expect(cloud).toContain("openai")
    expect(cloud).toContain("xai")
    expect(cloud).toContain("saeeol")
    expect(cloud).toContain("azure")
    expect(cloud).toContain("amazon-bedrock")
    expect(cloud).toContain("google-vertex")
    expect(cloud).toContain("google-vertex-anthropic")
    expect(cloud).toContain("github-copilot")
  })

  test("no overlap between categories", () => {
    const localSet = new Set(local)
    const customSet = new Set(custom)
    const cloudSet = new Set(cloud)

    for (const id of localSet) {
      expect(customSet.has(id as string)).toBe(false)
      expect(cloudSet.has(id as string)).toBe(false)
    }
    for (const id of customSet) {
      expect(localSet.has(id as string)).toBe(false)
      expect(cloudSet.has(id as string)).toBe(false)
    }
  })

  test("all arrays are non-empty", () => {
    expect(local.length).toBeGreaterThan(0)
    expect(custom.length).toBeGreaterThan(0)
    expect(cloud.length).toBeGreaterThan(0)
  })
})

describe("ProviderCategory schema", () => {
  test("ProviderCategory accepts valid values", () => {
    for (const val of ["local", "custom", "cloud"] as const) {
      const result = Schema.decodeSync(ProviderCategory)(val)
      expect(result).toBe(val)
    }
  })

  test("ProviderCategory rejects invalid values", () => {
    expect(() => Schema.decodeSync(ProviderCategory)("invalid" as any)).toThrow()
  })
})

describe("Info schema with category", () => {
  function makeInfo(cat: "local" | "custom" | "cloud") {
    return {
      id: "test" as any,
      name: "Test",
      category: cat,
      source: "config" as const,
      env: [] as string[],
      options: {} as Record<string, any>,
      models: {} as Record<string, any>,
    }
  }

  test("Info decodes with category field", () => {
    const parsed = Schema.decodeSync(Info)(makeInfo("cloud"))
    expect(parsed.category).toBe("cloud")
  })

  test("Info rejects missing category", () => {
    const missing = {
      id: "test" as any,
      name: "Test",
      source: "config" as const,
      env: [] as string[],
      options: {} as Record<string, any>,
      models: {} as Record<string, any>,
    }
    expect(() => Schema.decodeSync(Info)(missing as any)).toThrow()
  })

  test("Info accepts all valid categories", () => {
    for (const cat of ["local", "custom", "cloud"] as const) {
      expect(Schema.decodeSync(Info)(makeInfo(cat)).category).toBe(cat)
    }
  })
})

describe("getCategory classification", () => {
  function getCategory(id: string): "local" | "custom" | "cloud" {
    if (local.includes(id)) return "local"
    if (custom.includes(id)) return "custom"
    return "cloud"
  }

  test("local providers classified correctly", () => {
    expect(getCategory("ollama")).toBe("local")
    expect(getCategory("lmstudio")).toBe("local")
    expect(getCategory("vllm")).toBe("local")
    expect(getCategory("text-generation-webui")).toBe("local")
    expect(getCategory("llama.cpp")).toBe("local")
  })

  test("custom providers classified correctly", () => {
    expect(getCategory("openrouter")).toBe("custom")
    expect(getCategory("llmgateway")).toBe("custom")
    expect(getCategory("nvidia")).toBe("custom")
    expect(getCategory("vercel")).toBe("custom")
    expect(getCategory("cerebras")).toBe("custom")
  })

  test("cloud providers classified correctly", () => {
    expect(getCategory("anthropic")).toBe("cloud")
    expect(getCategory("openai")).toBe("cloud")
    expect(getCategory("saeeol")).toBe("cloud")
    expect(getCategory("github-copilot")).toBe("cloud")
  })

  test("unknown providers default to cloud", () => {
    expect(getCategory("some-new-provider")).toBe("cloud")
    expect(getCategory("future-ai")).toBe("cloud")
  })
})

describe("fromModelsDevProvider assigns category", () => {
  test("fromModelsDevProvider returns custom category", () => {
    const { fromModelsDevProvider } = require("@/provider/provider-schemas")
    const mockProvider = {
      id: "test-provider",
      name: "Test Provider",
      env: [],
      models: {
        "test-model": {
          id: "test-model",
          name: "Test Model",
          limit: { context: 4096, output: 2048 },
          cost: {},
        },
      },
    }
    const result = fromModelsDevProvider(mockProvider)
    expect(result.category).toBe("custom")
  })
})

describe("provider re-exports", () => {
  test("Provider exports ProviderCategory", () => {
    expect(Provider.ProviderCategory).toBeDefined()
  })

  test("Provider exports LOCAL_PROVIDERS", () => {
    expect(Provider.LOCAL_PROVIDERS).toBeDefined()
    expect(Provider.LOCAL_PROVIDERS).toContain("ollama")
  })

  test("Provider exports CUSTOM_PROVIDERS", () => {
    expect(Provider.CUSTOM_PROVIDERS).toBeDefined()
    expect(Provider.CUSTOM_PROVIDERS).toContain("openrouter")
  })

  test("Provider exports CLOUD_PROVIDERS", () => {
    expect(Provider.CLOUD_PROVIDERS).toBeDefined()
    expect(Provider.CLOUD_PROVIDERS).toContain("anthropic")
  })
})
