import { describe, expect, test } from "bun:test"
import { ModelV2 } from "@codilore/core/model"
import { ProviderV2 } from "@codilore/core/provider"
import { Schema } from "effect"
import { OpenApi } from "effect/unstable/httpapi"
import { PublicApi } from "../../src/server/routes/instance/httpapi/public"

type OpenApiSchema = {
  readonly $ref?: string
  readonly items?: OpenApiSchema
  readonly properties?: Record<string, OpenApiSchema>
}

type OpenApiSpec = {
  readonly components?: { readonly schemas?: Record<string, OpenApiSchema> }
  readonly paths: Record<
    string,
    {
      readonly get?: {
        readonly responses?: Record<string, { readonly content?: Record<string, { schema?: OpenApiSchema }> }>
      }
    }
  >
}

function responseSchema(spec: OpenApiSpec, path: string) {
  return spec.paths[path]?.get?.responses?.["200"]?.content?.["application/json"]?.schema
}

function componentName(ref: string | undefined) {
  return ref?.replace("#/components/schemas/", "")
}

describe("PublicApi v2 catalog redaction", () => {
  test("routes use redacted provider and model DTO schemas", () => {
    const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
    const provider = responseSchema(spec, "/api/provider/{providerID}")
    const providers = responseSchema(spec, "/api/provider")
    const models = responseSchema(spec, "/api/model")

    expect(componentName(provider?.$ref)).toBe("ProviderV2PublicInfo")
    expect(componentName(providers?.items?.$ref)).toBe("ProviderV2PublicInfo")
    expect(componentName(models?.items?.$ref)).toBe("ModelV2PublicInfo")

    const providerProperties = spec.components?.schemas?.ProviderV2PublicInfo?.properties
    const modelProperties = spec.components?.schemas?.ModelV2PublicInfo?.properties
    expect(providerProperties).not.toHaveProperty("request")
    expect(modelProperties).not.toHaveProperty("request")
    expect(JSON.stringify(providerProperties)).not.toMatch(/settings|headers|body|data/)
    expect(JSON.stringify(modelProperties)).not.toMatch(/settings|headers|body/)
  })

  test("DTOs sanitize provider and model API URLs", () => {
    const providerID = ProviderV2.ID.make("test")
    const providers = [
      new ProviderV2.Info({
        ...ProviderV2.Info.empty(providerID),
        api: {
          type: "native",
          url: "https://provider-user:provider-password@example.com:8443/provider/v1?api_key=provider-secret#fragment",
          settings: {},
        },
      }),
      new ProviderV2.Info({
        ...ProviderV2.Info.empty(providerID),
        api: {
          type: "aisdk",
          package: "@ai-sdk/openai",
          url: "https://provider-aisdk-user:provider-aisdk-password@example.com:8444/provider/aisdk?api_key=provider-aisdk-secret#fragment",
        },
      }),
    ].map((provider) => Schema.encodeSync(ProviderV2.PublicInfo)(ProviderV2.toPublic(provider)))
    const models = [
      new ModelV2.Info({
        ...ModelV2.Info.empty(providerID, ModelV2.ID.make("native")),
        api: {
          id: ModelV2.ID.make("native"),
          type: "native",
          url: "https://native-user:native-password@example.com:9443/native/v1?api_key=native-secret#fragment",
          settings: {},
        },
      }),
      new ModelV2.Info({
        ...ModelV2.Info.empty(providerID, ModelV2.ID.make("aisdk")),
        api: {
          id: ModelV2.ID.make("aisdk"),
          type: "aisdk",
          package: "@ai-sdk/openai",
          url: "https://aisdk-user:aisdk-password@example.com:10443/aisdk/v1?api_key=aisdk-secret#fragment",
        },
      }),
    ].map((model) => Schema.encodeSync(ModelV2.PublicInfo)(ModelV2.toPublic(model)))

    expect(providers.map((provider) => provider.api)).toEqual([
      { type: "native", url: "https://example.com:8443" },
      { type: "aisdk", package: "@ai-sdk/openai", url: "https://example.com:8444" },
    ])
    expect(models.map((model) => model.api)).toEqual([
      { id: "native", type: "native", url: "https://example.com:9443" },
      { id: "aisdk", type: "aisdk", package: "@ai-sdk/openai", url: "https://example.com:10443" },
    ])
    expect(JSON.stringify({ providers, models })).not.toMatch(/user|password|api_key|secret|fragment/)
  })

  test("DTOs omit malformed API URLs", () => {
    const providerID = ProviderV2.ID.make("test")
    const provider = Schema.encodeSync(ProviderV2.PublicInfo)(
      ProviderV2.toPublic(
        new ProviderV2.Info({
          ...ProviderV2.Info.empty(providerID),
          api: { type: "native", url: "not a url?api_key=provider-secret", settings: {} },
        }),
      ),
    )
    const modelID = ModelV2.ID.make("aisdk")
    const model = Schema.encodeSync(ModelV2.PublicInfo)(
      ModelV2.toPublic(
        new ModelV2.Info({
          ...ModelV2.Info.empty(providerID, modelID),
          api: { id: modelID, type: "aisdk", package: "@ai-sdk/openai", url: "model-secret" },
        }),
      ),
    )

    expect(provider.api).toEqual({ type: "native" })
    expect(model.api).toEqual({ id: "aisdk", type: "aisdk", package: "@ai-sdk/openai" })
    expect(JSON.stringify({ provider, model })).not.toMatch(/secret|api_key/)
  })
})
