package service

import (
	"fmt"
	"net/http"

	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
	"github.com/gin-gonic/gin"
)

// CodexOfficialClientsOnlyMessage 是 codex_cli_only 拒绝时面向客户端的通用兜底文案。
// 仅当拒绝原因不是「可解析版本但越界」（VersionTooLow/VersionTooHigh）时使用：
// 未命中官方/黑名单/缺指纹/版本无法识别都沿用这句（避免向伪装客户端泄露门控细节）。
const CodexOfficialClientsOnlyMessage = "This account only allows Codex official clients"

const (
	// CodexClientRestrictionReasonDisabled 表示账号未开启 codex_cli_only。
	CodexClientRestrictionReasonDisabled = "codex_cli_only_disabled"
	// CodexClientRestrictionReasonMatchedUA 表示请求命中官方客户端 UA 白名单。
	CodexClientRestrictionReasonMatchedUA = "official_client_user_agent_matched"
	// CodexClientRestrictionReasonMatchedOriginator 表示请求命中官方客户端 originator 白名单。
	CodexClientRestrictionReasonMatchedOriginator = "official_client_originator_matched"
	// CodexClientRestrictionReasonNotMatchedUA 表示请求未命中任何允许的客户端身份。
	CodexClientRestrictionReasonNotMatchedUA = "official_client_user_agent_not_matched"
	// CodexClientRestrictionReasonForceCodexCLI 表示通过 ForceCodexCLI 配置兜底放行。
	CodexClientRestrictionReasonForceCodexCLI = "force_codex_cli_enabled"
	// CodexClientRestrictionReasonBlacklisted 表示请求命中全局黑名单（门内 deny 最先，OR 语义）。
	CodexClientRestrictionReasonBlacklisted = "blacklist_matched"
	// CodexClientRestrictionReasonMatchedWhitelistClient 表示请求命中全局自由白名单条目（双因子 AND）。
	CodexClientRestrictionReasonMatchedWhitelistClient = "whitelist_client_matched"
	// CodexClientRestrictionReasonVersionTooLow 表示 UA 解析出的 Codex 引擎版本低于最低要求。
	CodexClientRestrictionReasonVersionTooLow = "codex_version_too_low"
	// CodexClientRestrictionReasonMissingEngineFingerprint 表示 strict 指纹门下缺少 codex 引擎指纹头。
	CodexClientRestrictionReasonMissingEngineFingerprint = "missing_engine_fingerprint"
	// CodexClientRestrictionReasonVersionUndetectable 表示 codex_cli_only 下无法从 UA 解析出 Codex 引擎版本。
	CodexClientRestrictionReasonVersionUndetectable = "codex_version_undetectable"
	// CodexClientRestrictionReasonVersionTooHigh 表示 UA 解析出的 Codex 引擎版本高于最高允许版本。
	CodexClientRestrictionReasonVersionTooHigh = "codex_version_too_high"
	// CodexClientRestrictionReasonMatchedAppServerClient 表示 App Server 开关开启时对未列名客户端开闸放行（仍过引擎门）。
	CodexClientRestrictionReasonMatchedAppServerClient = "app_server_client_matched"
)

// CodexRestrictionPolicy 是 codex_cli_only 判定所需的全局策略快照，由调用方从全局设置解析注入（global-only）。
// 账号侧只有 codex_cli_only 开关本身；黑/白名单、最低版本、指纹门均为全局设置。
type CodexRestrictionPolicy struct {
	Whitelist                []openai.AllowedClientEntry      // 全局自由白名单（双因子 AND，放行官方集未覆盖的 app-server client）
	Blacklist                []openai.AllowedClientEntry      // 全局自由黑名单（OR，宽 deny）
	MinCodexVersion          string                           // 最低 Codex 引擎版本 semver；""=不校验
	MaxCodexVersion          string                           // 最高 Codex 引擎版本 semver；""=不校验
	AllowAppServerClients    bool                             // App Server 开关：对未列名客户端开闸（仍受引擎门约束）
	EngineFingerprintSignals []openai.EngineFingerprintSignal // 引擎指纹门信号列表（勾选 AND / 行内变体 OR）；缺省=默认种子(只勾 x-codex-)
}

// CodexClientRestrictionDetectionResult 是 codex_cli_only 统一检测入口结果。
type CodexClientRestrictionDetectionResult struct {
	Enabled bool
	Matched bool
	Reason  string
	// DetectedVersion 是从官方 UA 解析出的 Codex 引擎版本；仅在版本门拒绝
	// (VersionTooLow / VersionTooHigh) 时填充，供面向客户端的差异化文案使用。
	DetectedVersion string
	// MinCodexVersion 是触发 VersionTooLow 时的最低要求版本（来自策略快照）。
	MinCodexVersion string
	// MaxCodexVersion 是触发 VersionTooHigh 时的最高允许版本（来自策略快照）。
	MaxCodexVersion string
}

// CodexClientRestrictionDetector 定义 codex_cli_only 统一检测入口。
type CodexClientRestrictionDetector interface {
	Detect(c *gin.Context, account *Account, policy CodexRestrictionPolicy, body []byte) CodexClientRestrictionDetectionResult
}

// OpenAICodexClientRestrictionDetector 为 OpenAI OAuth codex_cli_only 的默认实现。
type OpenAICodexClientRestrictionDetector struct {
	cfg *config.Config
}

func NewOpenAICodexClientRestrictionDetector(cfg *config.Config) *OpenAICodexClientRestrictionDetector {
	return &OpenAICodexClientRestrictionDetector{cfg: cfg}
}

// Detect 门控顺序（每步可短路）：
//  1. 账号未开 codex_cli_only → 不限制（Disabled）。
//  2. gateway.force_codex_cli → 全局旁路放行（ForceCodexCLI）。
//  3. 黑名单命中 → 立即拒（门内 deny 最先，OR 语义）。
//  4. 身份候选：官方 UA / 官方 originator / 全局白名单 / App Server 开闸（全局开关 OR 账号开关）；都不命中 → 拒（NotMatchedUA）。
//  5. Codex 版本（仅官方候选）：版本必须可解析（否则 VersionUndetectable）；< Min → 拒（TooLow）；> Max → 拒（TooHigh）。
//  6. 引擎指纹 AND 硬门：按 EngineFingerprintSignals 列表勾选 AND 判定（无任何 Required 信号→放行，即「关闭指纹门」=取消所有勾选）；白名单条目可显式 skip。
func (d *OpenAICodexClientRestrictionDetector) Detect(c *gin.Context, account *Account, policy CodexRestrictionPolicy, body []byte) CodexClientRestrictionDetectionResult {
	if account == nil || !account.IsCodexCLIOnlyEnabled() {
		return CodexClientRestrictionDetectionResult{Enabled: false, Matched: false, Reason: CodexClientRestrictionReasonDisabled}
	}

	if d != nil && d.cfg != nil && d.cfg.Gateway.ForceCodexCLI {
		return CodexClientRestrictionDetectionResult{Enabled: true, Matched: true, Reason: CodexClientRestrictionReasonForceCodexCLI}
	}

	userAgent := ""
	originator := ""
	var header http.Header
	if c != nil {
		userAgent = c.GetHeader("User-Agent")
		originator = c.GetHeader("originator")
		if c.Request != nil {
			header = c.Request.Header
		}
	}

	// 3. 黑名单优先（门内 deny 最先，OR：任一已声明字段命中即拒）。
	if openai.MatchDenyEntries(userAgent, originator, policy.Blacklist) {
		return CodexClientRestrictionDetectionResult{Enabled: true, Matched: false, Reason: CodexClientRestrictionReasonBlacklisted}
	}

	// 4. 身份候选（优先级：官方 > 全局白名单 > App Server 开闸：全局开关 OR 账号开关）。
	reason := ""
	skipFingerprint := false
	switch {
	case openai.IsCodexOfficialClientRequestStrict(userAgent):
		reason = CodexClientRestrictionReasonMatchedUA
	case openai.IsCodexOfficialClientOriginator(originator):
		reason = CodexClientRestrictionReasonMatchedOriginator
	default:
		if entry, ok := openai.MatchClientEntry(userAgent, originator, policy.Whitelist); ok {
			reason = CodexClientRestrictionReasonMatchedWhitelistClient
			skipFingerprint = entry.SkipEngineFingerprint
		} else if policy.AllowAppServerClients || account.IsCodexCLIOnlyAppServerAllowed() {
			reason = CodexClientRestrictionReasonMatchedAppServerClient
		}
	}
	if reason == "" {
		return CodexClientRestrictionDetectionResult{Enabled: true, Matched: false, Reason: CodexClientRestrictionReasonNotMatchedUA}
	}

	// 5. Codex 版本：仅对官方候选（官方 UA / 官方 originator）。版本必须可识别（需求②），再校验 [min,max]。
	//    白名单/账号预设/App Server 候选可能不带可解析引擎版本，整块跳过。
	if reason == CodexClientRestrictionReasonMatchedUA || reason == CodexClientRestrictionReasonMatchedOriginator {
		ver, ok := openai.ParseCodexEngineVersion(userAgent)
		if !ok {
			return CodexClientRestrictionDetectionResult{Enabled: true, Matched: false, Reason: CodexClientRestrictionReasonVersionUndetectable}
		}
		if policy.MinCodexVersion != "" && CompareVersions(ver, policy.MinCodexVersion) < 0 {
			return CodexClientRestrictionDetectionResult{
				Enabled:         true,
				Matched:         false,
				Reason:          CodexClientRestrictionReasonVersionTooLow,
				DetectedVersion: ver,
				MinCodexVersion: policy.MinCodexVersion,
			}
		}
		if policy.MaxCodexVersion != "" && CompareVersions(ver, policy.MaxCodexVersion) > 0 {
			return CodexClientRestrictionDetectionResult{
				Enabled:         true,
				Matched:         false,
				Reason:          CodexClientRestrictionReasonVersionTooHigh,
				DetectedVersion: ver,
				MaxCodexVersion: policy.MaxCodexVersion,
			}
		}
	}

	// 6. 引擎指纹 AND 硬门。对所有候选生效;唯一例外:命中的白名单条目显式 SkipEngineFingerprint。
	//    按全局信号列表判定:所有勾选(Required)信号都命中即放行,每条命中任一变体即满足(行内 OR);
	//    无任何勾选信号 → 视为无要求放行(即「关闭指纹门」=取消所有勾选)。ForceCodexCLI 与黑名单不经此门。
	if !skipFingerprint {
		if !openai.EvaluateEngineFingerprint(header, body, policy.EngineFingerprintSignals) {
			return CodexClientRestrictionDetectionResult{Enabled: true, Matched: false, Reason: CodexClientRestrictionReasonMissingEngineFingerprint}
		}
	}

	return CodexClientRestrictionDetectionResult{Enabled: true, Matched: true, Reason: reason}
}

// CodexClientRestrictionMessage 把检测结果映射为面向客户端的 403 文案。
// 仅版本越界（VersionTooLow/VersionTooHigh）给出带实际版本号与边界的差异化提示——
// 这类请求其实已被识别为官方 Codex（命中官方 UA/originator），再回「只允许官方客户端」会误导；
// 其余拒绝原因统一沿用通用兜底句，不暴露门控细节。
func CodexClientRestrictionMessage(r CodexClientRestrictionDetectionResult) string {
	switch r.Reason {
	case CodexClientRestrictionReasonVersionTooLow:
		return fmt.Sprintf(
			"Your Codex version (%s) is below the minimum required version (%s). Please update Codex.",
			r.DetectedVersion, r.MinCodexVersion)
	case CodexClientRestrictionReasonVersionTooHigh:
		return fmt.Sprintf(
			"Your Codex version (%s) exceeds the maximum allowed version (%s). Please downgrade Codex to %s or lower.",
			r.DetectedVersion, r.MaxCodexVersion, r.MaxCodexVersion)
	default:
		return CodexOfficialClientsOnlyMessage
	}
}
