package runner

import (
	"fmt"
	"strings"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

const unsupportedRequestResolutionCode = "unsupported_request_resolution"
const unsupportedRequestAspectRatioCode = "unsupported_request_aspect_ratio"

type requestResolutionRequirement struct {
	Kind           string
	RequestedModel string
	ModelType      string
	Resolution     string
	Source         string
	Scopes         []string
}

type videoResolutionReferenceStats struct {
	HasFirstFrame      bool
	HasLastFrame       bool
	ReferenceImages    int
	HasReferenceVideo  bool
	HasReferenceAudio  bool
	HasAnyMedia        bool
	HasExplicitContent bool
}

func filterRuntimeCandidatesByRequest(kind string, requestedModel string, modelType string, body map[string]any, candidates []store.RuntimeModelCandidate) ([]store.RuntimeModelCandidate, map[string]any, error) {
	filtered, resolutionSummary, err := filterRuntimeCandidatesByRequestResolution(kind, requestedModel, modelType, body, candidates)
	if err != nil {
		return filtered, resolutionSummary, err
	}
	filtered, aspectRatioSummary, err := filterRuntimeCandidatesByRequestAspectRatio(kind, requestedModel, modelType, body, filtered)
	return filtered, mergeCandidateFilterSummaries(resolutionSummary, aspectRatioSummary), err
}

func filterRuntimeCandidatesByRequestResolution(kind string, requestedModel string, modelType string, body map[string]any, candidates []store.RuntimeModelCandidate) ([]store.RuntimeModelCandidate, map[string]any, error) {
	requirement, ok := requestResolutionRequirementFor(kind, requestedModel, modelType, body)
	if !ok || len(candidates) == 0 {
		return candidates, nil, nil
	}

	filtered := make([]store.RuntimeModelCandidate, 0, len(candidates))
	rejected := make([]map[string]any, 0)
	supportedResolutions := make([]string, 0)
	for _, candidate := range candidates {
		supported, detail := candidateSupportsRequestResolution(candidate, requirement)
		if supported {
			filtered = append(filtered, candidate)
			for _, value := range stringListFromAny(detail["allowedResolutions"]) {
				appendUniqueString(&supportedResolutions, value)
			}
			continue
		}
		rejected = append(rejected, detail)
		for _, value := range stringListFromAny(detail["allowedResolutions"]) {
			appendUniqueString(&supportedResolutions, value)
		}
	}

	summary := requestResolutionFilterSummary(requirement, len(candidates), len(filtered), rejected, supportedResolutions)
	if len(filtered) == 0 {
		return nil, summary, &store.ModelCandidateUnavailableError{
			Code:    unsupportedRequestResolutionCode,
			Message: unsupportedRequestResolutionMessage(requirement, rejected),
			Details: summary,
		}
	}
	return filtered, summary, nil
}

func requestResolutionRequirementFor(kind string, requestedModel string, modelType string, body map[string]any) (requestResolutionRequirement, bool) {
	if !isResolutionFilteredModelType(modelType) {
		return requestResolutionRequirement{}, false
	}
	resolution, source := requestResolutionValue(body, modelType)
	if resolution == "" {
		return requestResolutionRequirement{}, false
	}
	return requestResolutionRequirement{
		Kind:           kind,
		RequestedModel: requestedModel,
		ModelType:      modelType,
		Resolution:     resolution,
		Source:         source,
		Scopes:         requestResolutionScopes(body, modelType),
	}, true
}

func requestResolutionValue(body map[string]any, modelType string) (string, string) {
	if value := normalizedRequestResolution(stringFromAny(body["resolution"]), modelType); value != "" {
		if _, _, ok := parsePixelSizeString(value); ok {
			return "", ""
		}
		return value, "resolution"
	}
	size := normalizedRequestResolution(stringFromAny(body["size"]), modelType)
	if size == "" {
		return "", ""
	}
	if isImageResolution(modelType, size) || isVideoResolution(modelType, size) {
		return size, "size"
	}
	return "", ""
}

func normalizedRequestResolution(value string, modelType string) string {
	value = strings.TrimSpace(value)
	if value == "" || isEmptyParamString(value) {
		return ""
	}
	switch strings.ToLower(value) {
	case "auto", "automatic", "adaptive", "default":
		return ""
	}
	if _, _, ok := parsePixelSizeString(value); ok {
		return value
	}
	return normalizeMediaResolution(modelType, value)
}

func isResolutionFilteredModelType(modelType string) bool {
	return modelType == "image_generate" || modelType == "image_edit" || isVideoModelType(modelType)
}

func candidateSupportsRequestResolution(candidate store.RuntimeModelCandidate, requirement requestResolutionRequirement) (bool, map[string]any) {
	modelType := firstNonEmptyString(candidate.ModelType, requirement.ModelType)
	capability := capabilityForType(effectiveModelCapability(candidate), modelType)
	detail := candidateResolutionDetail(candidate, requirement, modelType)
	if capability == nil {
		detail["reason"] = "capability_unrestricted"
		detail["message"] = "候选平台模型未配置分辨率白名单，按未限制处理。"
		return true, detail
	}

	allowed, configured := outputResolutionAllowedValues(capability["output_resolutions"], requirement.Scopes)
	detail["allowedResolutions"] = allowed
	detail["capabilityPath"] = capabilityPath(modelType, "output_resolutions")
	detail["capabilityValue"] = cloneAny(capability["output_resolutions"])
	if !configured {
		detail["reason"] = "output_resolutions_unrestricted"
		detail["message"] = "候选平台模型未声明 output_resolutions，按未限制处理。"
		return true, detail
	}
	if len(allowed) == 0 {
		if _, scoped := capability["output_resolutions"].(map[string]any); scoped && len(requirement.Scopes) > 0 && !outputResolutionScopeConfigured(capability["output_resolutions"], requirement.Scopes) {
			detail["reason"] = "resolution_scope_not_allowed"
			detail["message"] = "候选平台模型未声明当前请求场景的 output_resolutions。"
			return false, detail
		}
		detail["reason"] = "output_resolutions_empty"
		detail["message"] = "候选平台模型声明了空分辨率白名单，按未限制处理。"
		return true, detail
	}
	if containsResolution(allowed, requirement.Resolution) {
		detail["reason"] = "supported"
		return true, detail
	}
	detail["reason"] = "resolution_not_allowed"
	detail["message"] = "请求分辨率不在候选平台模型 output_resolutions 中。"
	return false, detail
}

func outputResolutionAllowedValues(value any, scopes []string) ([]string, bool) {
	switch typed := value.(type) {
	case []any, []string, string:
		return uniqueStringList(stringListFromAny(typed)), true
	case map[string]any:
		for _, scope := range append(scopes, "default", "*", "all") {
			if scope == "" {
				continue
			}
			if raw, ok := typed[scope]; ok {
				return uniqueStringList(stringListFromAny(raw)), true
			}
		}
		if len(scopes) == 0 {
			values := make([]string, 0)
			for _, raw := range typed {
				values = append(values, stringListFromAny(raw)...)
			}
			return uniqueStringList(values), true
		}
		return nil, true
	default:
		return nil, false
	}
}

func outputResolutionScopeConfigured(value any, scopes []string) bool {
	typed, ok := value.(map[string]any)
	if !ok {
		return false
	}
	for _, scope := range append(append([]string(nil), scopes...), "default", "*", "all") {
		if scope == "" {
			continue
		}
		if _, exists := typed[scope]; exists {
			return true
		}
	}
	return false
}

type requestAspectRatioRequirement struct {
	Kind           string
	RequestedModel string
	ModelType      string
	AspectRatio    string
	Source         string
	Resolution     string
}

func filterRuntimeCandidatesByRequestAspectRatio(kind string, requestedModel string, modelType string, body map[string]any, candidates []store.RuntimeModelCandidate) ([]store.RuntimeModelCandidate, map[string]any, error) {
	requirement, ok := requestAspectRatioRequirementFor(kind, requestedModel, modelType, body)
	if !ok || len(candidates) == 0 {
		return candidates, nil, nil
	}

	filtered := make([]store.RuntimeModelCandidate, 0, len(candidates))
	rejected := make([]map[string]any, 0)
	supportedRatios := make([]string, 0)
	for _, candidate := range candidates {
		modelType := firstNonEmptyString(candidate.ModelType, requirement.ModelType)
		capability := capabilityForType(effectiveModelCapability(candidate), modelType)
		detail := candidateAspectRatioDetail(candidate, requirement, modelType)
		if capability == nil {
			detail["reason"] = "capability_unrestricted"
			filtered = append(filtered, candidate)
			continue
		}
		allowed, configured := outputResolutionAllowedValues(capability["aspect_ratio_allowed"], []string{requirement.Resolution})
		detail["allowedAspectRatios"] = allowed
		detail["capabilityPath"] = capabilityPath(modelType, "aspect_ratio_allowed")
		detail["capabilityValue"] = cloneAny(capability["aspect_ratio_allowed"])
		for _, value := range allowed {
			appendUniqueString(&supportedRatios, value)
		}
		if !configured || len(allowed) == 0 || containsResolution(allowed, requirement.AspectRatio) {
			detail["reason"] = "supported"
			filtered = append(filtered, candidate)
			continue
		}
		detail["reason"] = "aspect_ratio_not_allowed"
		detail["message"] = "请求比例不在候选平台模型 aspect_ratio_allowed 中。"
		rejected = append(rejected, detail)
	}

	summary := map[string]any{
		"code":                    unsupportedRequestAspectRatioCode,
		"filter":                  "request_aspect_ratio",
		"kind":                    requirement.Kind,
		"requestedModel":          requirement.RequestedModel,
		"modelType":               requirement.ModelType,
		"requestedAspectRatio":    requirement.AspectRatio,
		"aspectRatioSource":       requirement.Source,
		"resolution":              requirement.Resolution,
		"capabilityPath":          capabilityPath(requirement.ModelType, "aspect_ratio_allowed"),
		"candidateCount":          len(candidates),
		"supportedCandidateCount": len(filtered),
		"filteredCandidateCount":  len(rejected),
		"supportedAspectRatios":   uniqueStringList(supportedRatios),
		"rejectedCandidates":      rejected,
	}
	if len(filtered) == 0 {
		return nil, summary, &store.ModelCandidateUnavailableError{
			Code:    unsupportedRequestAspectRatioCode,
			Message: fmt.Sprintf("请求的%s比例 %s 没有可用平台模型支持，已过滤 %d 个候选平台模型", mediaResourceName(requirement.ModelType), requirement.AspectRatio, len(rejected)),
			Details: summary,
		}
	}
	return filtered, summary, nil
}

func requestAspectRatioRequirementFor(kind string, requestedModel string, modelType string, body map[string]any) (requestAspectRatioRequirement, bool) {
	if !isResolutionFilteredModelType(modelType) {
		return requestAspectRatioRequirement{}, false
	}
	for _, key := range []string{"aspect_ratio", "aspectRatio", "ratio"} {
		raw, exists := body[key]
		if !exists {
			continue
		}
		aspectRatio, ok := normalizedAspectRatio(stringFromAny(raw))
		if !ok {
			return requestAspectRatioRequirement{}, false
		}
		resolution, _ := requestResolutionValue(body, modelType)
		return requestAspectRatioRequirement{
			Kind:           kind,
			RequestedModel: requestedModel,
			ModelType:      modelType,
			AspectRatio:    aspectRatio,
			Source:         key,
			Resolution:     resolution,
		}, true
	}
	return requestAspectRatioRequirement{}, false
}

func candidateAspectRatioDetail(candidate store.RuntimeModelCandidate, requirement requestAspectRatioRequirement, modelType string) map[string]any {
	return map[string]any{
		"platformId":        candidate.PlatformID,
		"platformKey":       candidate.PlatformKey,
		"platformName":      candidate.PlatformName,
		"provider":          candidate.Provider,
		"platformModelId":   candidate.PlatformModelID,
		"modelName":         candidate.ModelName,
		"modelAlias":        candidate.ModelAlias,
		"displayName":       candidate.DisplayName,
		"providerModelName": candidate.ProviderModelName,
		"modelType":         modelType,
		"requested": map[string]any{
			"aspectRatio": requirement.AspectRatio,
			"source":      requirement.Source,
			"resolution":  requirement.Resolution,
		},
	}
}

func mediaResourceName(modelType string) string {
	if modelType == "image_generate" || modelType == "image_edit" {
		return "图像"
	}
	if isVideoModelType(modelType) {
		return "视频"
	}
	return "媒体"
}

func containsResolution(values []string, target string) bool {
	for _, value := range values {
		if strings.EqualFold(strings.TrimSpace(value), strings.TrimSpace(target)) {
			return true
		}
	}
	return false
}

func candidateResolutionDetail(candidate store.RuntimeModelCandidate, requirement requestResolutionRequirement, modelType string) map[string]any {
	return map[string]any{
		"platformId":        candidate.PlatformID,
		"platformKey":       candidate.PlatformKey,
		"platformName":      candidate.PlatformName,
		"provider":          candidate.Provider,
		"platformModelId":   candidate.PlatformModelID,
		"modelName":         candidate.ModelName,
		"modelAlias":        candidate.ModelAlias,
		"displayName":       candidate.DisplayName,
		"providerModelName": candidate.ProviderModelName,
		"modelType":         modelType,
		"requested": map[string]any{
			"resolution": requirement.Resolution,
			"source":     requirement.Source,
			"scopes":     requirement.Scopes,
		},
	}
}

func requestResolutionFilterSummary(requirement requestResolutionRequirement, candidateCount int, supportedCandidateCount int, rejected []map[string]any, supportedResolutions []string) map[string]any {
	return map[string]any{
		"code":                    unsupportedRequestResolutionCode,
		"filter":                  "request_resolution",
		"kind":                    requirement.Kind,
		"requestedModel":          requirement.RequestedModel,
		"modelType":               requirement.ModelType,
		"requestedResolution":     requirement.Resolution,
		"resolutionSource":        requirement.Source,
		"resolutionScopes":        requirement.Scopes,
		"capabilityPath":          capabilityPath(requirement.ModelType, "output_resolutions"),
		"candidateCount":          candidateCount,
		"supportedCandidateCount": supportedCandidateCount,
		"filteredCandidateCount":  len(rejected),
		"supportedResolutions":    uniqueStringList(supportedResolutions),
		"rejectedCandidates":      rejected,
	}
}

func unsupportedRequestResolutionMessage(requirement requestResolutionRequirement, rejected []map[string]any) string {
	resource := "媒体"
	if requirement.ModelType == "image_generate" || requirement.ModelType == "image_edit" {
		resource = "图像"
	} else if isVideoModelType(requirement.ModelType) {
		resource = "视频"
	}
	message := fmt.Sprintf("请求的%s分辨率 %s 没有可用平台模型支持，已过滤 %d 个候选平台模型", resource, requirement.Resolution, len(rejected))
	if summaries := rejectedResolutionSummaries(rejected, 3); len(summaries) > 0 {
		message += "；候选支持：" + strings.Join(summaries, "；")
	}
	return message
}

func rejectedResolutionSummaries(rejected []map[string]any, limit int) []string {
	summaries := make([]string, 0, limit)
	for _, item := range rejected {
		if len(summaries) >= limit {
			break
		}
		allowed := stringListFromAny(item["allowedResolutions"])
		if len(allowed) == 0 {
			continue
		}
		name := firstNonEmptyString(stringFromAny(item["platformName"]), stringFromAny(item["platformKey"]), stringFromAny(item["provider"]))
		model := firstNonEmptyString(stringFromAny(item["displayName"]), stringFromAny(item["modelAlias"]), stringFromAny(item["modelName"]))
		if model != "" {
			name = firstNonEmptyString(name, model)
			if name != model {
				name += "/" + model
			}
		}
		if name == "" {
			name = "候选"
		}
		summaries = append(summaries, fmt.Sprintf("%s=%s", name, strings.Join(allowed, "/")))
	}
	return summaries
}

func requestResolutionScopes(body map[string]any, modelType string) []string {
	if !isVideoModelType(modelType) {
		return nil
	}
	scopes := make([]string, 0)
	for _, key := range []string{"videoMode", "video_mode", "mode", "generation_mode", "generate_mode", "supported_mode"} {
		appendUniqueString(&scopes, stringFromMap(body, key))
	}
	stats := videoResolutionReferenceStatsFromBody(body)
	if stats.HasFirstFrame && stats.HasLastFrame {
		appendUniqueString(&scopes, "input_first_last_frame")
		appendUniqueString(&scopes, "first_last_frame")
	} else if stats.HasFirstFrame {
		appendUniqueString(&scopes, "input_first_frame")
	} else if stats.HasLastFrame {
		appendUniqueString(&scopes, "input_last_frame")
	}
	if stats.ReferenceImages > 1 {
		appendUniqueString(&scopes, "input_reference_generate_multiple")
		appendUniqueString(&scopes, "image_reference")
	} else if stats.ReferenceImages == 1 {
		appendUniqueString(&scopes, "input_reference_generate_single")
		appendUniqueString(&scopes, "image_reference")
	}
	if stats.HasReferenceVideo {
		appendUniqueString(&scopes, "video_reference")
	}
	if stats.HasReferenceAudio {
		appendUniqueString(&scopes, "audio_reference")
	}
	if !stats.HasAnyMedia {
		appendUniqueString(&scopes, "text_to_video")
	}
	return scopes
}

func videoResolutionReferenceStatsFromBody(body map[string]any) videoResolutionReferenceStats {
	stats := videoResolutionReferenceStats{}
	content := contentItems(body["content"])
	stats.HasExplicitContent = len(content) > 0
	for _, item := range content {
		if isImageContent(item) {
			stats.HasAnyMedia = true
			switch strings.TrimSpace(stringFromAny(item["role"])) {
			case "first_frame":
				stats.HasFirstFrame = true
			case "last_frame":
				stats.HasLastFrame = true
			default:
				stats.ReferenceImages++
			}
		}
		if isVideoContent(item) {
			stats.HasAnyMedia = true
			stats.HasReferenceVideo = true
		}
		if isAudioContent(item) {
			stats.HasAnyMedia = true
			stats.HasReferenceAudio = true
		}
	}
	if hasAnyString(body, "first_frame", "firstFrame") {
		stats.HasAnyMedia = true
		stats.HasFirstFrame = true
	}
	if hasAnyString(body, "last_frame", "lastFrame") {
		stats.HasAnyMedia = true
		stats.HasLastFrame = true
	}
	if hasAnyString(body, "reference_image", "referenceImage") {
		stats.HasAnyMedia = true
		stats.ReferenceImages++
	}
	if hasAnyString(body, "video", "video_url", "videoUrl", "reference_video", "referenceVideo") {
		stats.HasAnyMedia = true
		stats.HasReferenceVideo = true
	}
	if hasAnyString(body, "audio_url", "audioUrl", "reference_audio", "referenceAudio") {
		stats.HasAnyMedia = true
		stats.HasReferenceAudio = true
	}
	if hasAnyString(body, "image", "images", "image_url", "imageUrl", "image_urls", "imageUrls") {
		stats.HasAnyMedia = true
		if !stats.HasFirstFrame && !stats.HasExplicitContent {
			stats.HasFirstFrame = true
		} else {
			stats.ReferenceImages++
		}
	}
	return stats
}

func candidateCapabilityFilterMetrics(summary map[string]any) map[string]any {
	if len(summary) == 0 {
		return nil
	}
	return map[string]any{"candidateCapabilityFilter": summary}
}
