//go:build unit

package service

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func newOpenAIOAuthAccountForModelTest() *Account {
	return &Account{
		ID:       1,
		Platform: PlatformOpenAI,
		Type:     AccountTypeOAuth,
	}
}

func TestIsModelSupported_OpenAIOAuthEmptyMapping_ServableModels(t *testing.T) {
	account := newOpenAIOAuthAccountForModelTest()

	servable := []string{
		"", // 空模型交由上层必填校验
		"gpt-5.4",
		"gpt-5.4-high", // 推理后缀变体
		"gpt-5.3-codex",
		"gpt-5.1-codex-mini",
		"gpt-5",
		"codex-mini-latest",
		"gpt5.3codexspark",  // 别名拼写
		"gpt-image-1",       // 图像生成模型
		"claude-sonnet-4-6", // /v1/messages 调度默认映射兜底
		"claude-3-opus-20240229",
		"gpt-4o",          // 保守 fail-open：非黑名单模型保持允许
		"my-custom-alias", // 自定义别名可能由渠道级映射在转发前改写，保持允许
	}
	for _, model := range servable {
		require.True(t, account.IsModelSupported(model), "expected %q to be servable by empty-mapping OpenAI OAuth account", model)
	}
}

func TestIsModelSupported_OpenAIOAuthEmptyMapping_RejectsForeignModels(t *testing.T) {
	account := newOpenAIOAuthAccountForModelTest()

	// Codex 上游必然以不可重试的 400 拒绝这些厂商家族；调度阶段就应跳过
	// 该账号，让显式声明支持的 API Key 账号接手（#3662）。
	foreign := []string{
		"deepseek-v4",
		"deepseek-chat",
		"glm-4.7",
		"kimi-k2",
		"k3",          // Kimi Code bare ID（无厂商前缀，需精确拒绝）
		"k3-256k",     // Kimi Code bare ID
		"provider/k3", // vendor/model 取 last segment 后仍为 k3
		"moonshot-v1-128k",
		"gemini-3.0-pro",
		"grok-4",
		"qwen3-max",
		"minimax-m2.5",
		"llama-3.3-70b",
		"provider/deepseek-v4", // vendor/model 形式取最后一段判定
	}
	for _, model := range foreign {
		require.False(t, account.IsModelSupported(model), "expected %q to be rejected by empty-mapping OpenAI OAuth account", model)
	}
}

func TestIsModelSupported_OpenAIOAuthExplicitMappingUnchanged(t *testing.T) {
	account := newOpenAIOAuthAccountForModelTest()
	account.Credentials = map[string]any{
		"model_mapping": map[string]any{
			"deepseek-v4": "gpt-5.4",
			"k3":          "gpt-5.4", // 显式映射优先：bare k3 仍可被账号声明支持
		},
	}

	// 显式映射沿用原有语义：命中映射即支持，未命中即不支持。
	require.True(t, account.IsModelSupported("deepseek-v4"))
	require.True(t, account.IsModelSupported("k3"))
	require.False(t, account.IsModelSupported("glm-4.7"))
}

func TestIsModelSupported_OpenAIOAuthPassthroughAllowsAll(t *testing.T) {
	account := newOpenAIOAuthAccountForModelTest()
	account.Extra = map[string]any{"openai_passthrough": true}

	// 透传模式仅替换认证，模型语义由上游决定，保持"允许所有"。
	require.True(t, account.IsModelSupported("deepseek-v4"))
}

func TestIsModelSupported_OpenAIOAuthPassthroughIgnoresLeftoverMapping(t *testing.T) {
	account := newOpenAIOAuthAccountForModelTest()
	account.Extra = map[string]any{"openai_passthrough": true}
	// 账号从"白名单模式"切到透传后，credentials 里常残留旧的非空 model_mapping。
	// 透传应无视该白名单，放行不在其中的模型（issue #4936）；否则透传账号会被
	// 调度期的 IsModelSupported 排除，客户端收到 404 "not supported by any account"。
	account.Credentials = map[string]any{
		"model_mapping": map[string]any{"gpt-5.4": "gpt-5.4"},
	}

	require.True(t, account.IsModelSupported("gpt-5.6-sol"), "透传应放行不在残留白名单中的新模型")
	require.True(t, account.IsModelSupported("deepseek-v4"), "透传应放行任意模型")
}

func TestIsModelSupported_OpenAIAPIKeyEmptyMappingAllowsAll(t *testing.T) {
	account := &Account{
		ID:       2,
		Platform: PlatformOpenAI,
		Type:     AccountTypeAPIKey,
	}

	// API Key 账号（第三方 OpenAI 兼容上游）可服务任意别名，语义不变。
	require.True(t, account.IsModelSupported("deepseek-v4"))
	require.True(t, account.IsModelSupported("gpt-5.4"))
}

func TestIsModelSupported_NonOpenAIPlatformsUnchanged(t *testing.T) {
	anthropic := &Account{ID: 3, Platform: PlatformAnthropic, Type: AccountTypeOAuth}
	require.True(t, anthropic.IsModelSupported("claude-sonnet-4-6"))
	require.True(t, anthropic.IsModelSupported("deepseek-v4"))
}

func TestIsOpenAIOAuthServableModel(t *testing.T) {
	require.True(t, isOpenAIOAuthServableModel("gpt-5.4-high"))
	require.True(t, isOpenAIOAuthServableModel("  gpt-5.3-codex  "))
	require.True(t, isOpenAIOAuthServableModel("claude-3-5-haiku-20241022"))
	require.True(t, isOpenAIOAuthServableModel("DeepThink-x"))  // 非黑名单前缀，保持允许
	require.False(t, isOpenAIOAuthServableModel("DeepSeek-V4")) // 大小写不敏感
	require.False(t, isOpenAIOAuthServableModel("qwen3-235b-thinking"))
	require.True(t, isOpenAIOAuthServableModel("deepseekcoder")) // 无连字符 → 非黑名单前缀，保持允许
	require.False(t, isOpenAIOAuthServableModel("k3"))
	require.False(t, isOpenAIOAuthServableModel("k3-256k"))
	require.False(t, isOpenAIOAuthServableModel("provider/k3"))
	require.True(t, isOpenAIOAuthServableModel("my-k3-alias")) // 非精确 bare ID，自定义别名 fail-open
}
