package runner

import (
	"errors"
	"strings"
	"testing"

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

func TestFilterRuntimeCandidatesByRequestResolutionKeepsSupportedCandidate(t *testing.T) {
	candidates := []store.RuntimeModelCandidate{
		candidateWithResolutions("low", "720p"),
		candidateWithResolutions("high", "1080p"),
	}

	filtered, summary, err := filterRuntimeCandidatesByRequest("videos.generations", "demo-video", "video_generate", map[string]any{
		"resolution": "1080p",
	}, candidates)
	if err != nil {
		t.Fatalf("filter should keep a supported candidate: %v", err)
	}
	if len(filtered) != 1 || filtered[0].PlatformKey != "high" {
		t.Fatalf("expected only high resolution candidate, got %+v", filtered)
	}
	if summary["filteredCandidateCount"] != 1 || summary["supportedCandidateCount"] != 1 {
		t.Fatalf("unexpected filter summary: %+v", summary)
	}
}

func TestFilterRuntimeCandidatesUsesStandardVideoResolutionValues(t *testing.T) {
	candidates := []store.RuntimeModelCandidate{
		{
			PlatformID:      "minimax",
			PlatformKey:     "minimax",
			PlatformName:    "MiniMax",
			PlatformModelID: "hailuo23",
			ModelName:       "MiniMax-Hailuo-2.3",
			ModelAlias:      "海螺2.3",
			ModelType:       "video_generate",
			Capabilities: map[string]any{
				"video_generate": map[string]any{
					"output_resolutions": []any{"720p", "1080p"},
				},
			},
		},
	}

	filtered, summary, err := filterRuntimeCandidatesByRequest("videos.generations", "海螺2.3", "video_generate", map[string]any{
		"resolution": "720p",
	}, candidates)
	if err != nil {
		t.Fatalf("standard 720p request should match MiniMax catalog capability before client-side upstream mapping: %v", err)
	}
	if len(filtered) != 1 || filtered[0].PlatformModelID != "hailuo23" {
		t.Fatalf("expected MiniMax candidate to remain, got %+v", filtered)
	}
	if summary["supportedCandidateCount"] != 1 || summary["filteredCandidateCount"] != 0 {
		t.Fatalf("unexpected filter summary: %+v", summary)
	}
}

func TestFilterRuntimeCandidatesByScopedVideoResolution(t *testing.T) {
	candidates := []store.RuntimeModelCandidate{
		{
			PlatformID:      "platform-first",
			PlatformKey:     "first",
			PlatformName:    "First Frame Platform",
			PlatformModelID: "model-first",
			ModelName:       "demo-video",
			ModelType:       "image_to_video",
			Capabilities: map[string]any{
				"image_to_video": map[string]any{
					"output_resolutions": map[string]any{
						"input_first_frame": []any{"1080p"},
					},
				},
			},
		},
		{
			PlatformID:      "platform-first-last",
			PlatformKey:     "first-last",
			PlatformName:    "First Last Platform",
			PlatformModelID: "model-first-last",
			ModelName:       "demo-video",
			ModelType:       "image_to_video",
			Capabilities: map[string]any{
				"image_to_video": map[string]any{
					"output_resolutions": map[string]any{
						"input_first_last_frame": []any{"1080p"},
					},
				},
			},
		},
	}

	filtered, _, err := filterRuntimeCandidatesByRequest("videos.generations", "demo-video", "image_to_video", map[string]any{
		"resolution": "1080p",
		"content": []any{
			map[string]any{"type": "image_url", "role": "first_frame", "image_url": map[string]any{"url": "https://example.com/first.png"}},
			map[string]any{"type": "image_url", "role": "last_frame", "image_url": map[string]any{"url": "https://example.com/last.png"}},
		},
	}, candidates)
	if err != nil {
		t.Fatalf("filter should keep first-last scoped candidate: %v", err)
	}
	if len(filtered) != 1 || filtered[0].PlatformKey != "first-last" {
		t.Fatalf("expected first-last scoped candidate only, got %+v", filtered)
	}
}

func TestFilterRuntimeCandidatesByRequestResolutionFailsWithDetails(t *testing.T) {
	candidates := []store.RuntimeModelCandidate{
		candidateWithImageResolutions("jimeng-v3", "1K", "2K"),
		candidateWithImageResolutions("jimeng-v4", "1K"),
	}

	filtered, summary, err := filterRuntimeCandidatesByRequest("images.generations", "demo-image", "image_generate", map[string]any{
		"resolution": "4K",
	}, candidates)
	if len(filtered) != 0 {
		t.Fatalf("expected no candidates, got %+v", filtered)
	}
	var candidateErr *store.ModelCandidateUnavailableError
	if !errors.As(err, &candidateErr) {
		t.Fatalf("expected model candidate error, got %T %v", err, err)
	}
	if candidateErr.Code != unsupportedRequestResolutionCode {
		t.Fatalf("unexpected error code: %s", candidateErr.Code)
	}
	if !strings.Contains(candidateErr.Message, "4K") {
		t.Fatalf("message should include requested resolution, got %q", candidateErr.Message)
	}
	if summary["filteredCandidateCount"] != 2 || candidateErr.Details["requestedResolution"] != "4K" {
		t.Fatalf("unexpected filter detail summary=%+v details=%+v", summary, candidateErr.Details)
	}
	if details := store.ModelCandidateErrorDetails(err); details["requestedResolution"] != "4K" {
		t.Fatalf("store detail helper should expose requested resolution, got %+v", details)
	}
}

func TestFilterRuntimeCandidatesSkipsPixelSizeCompatibility(t *testing.T) {
	candidates := []store.RuntimeModelCandidate{{
		PlatformID:      "openai",
		PlatformKey:     "openai",
		PlatformModelID: "gpt-image-1",
		ModelName:       "gpt-image-1",
		ModelType:       "image_generate",
		Capabilities: map[string]any{
			"image_generate": map[string]any{
				"aspect_ratio_allowed": []any{"1:1"},
			},
		},
	}}

	for name, body := range map[string]map[string]any{
		"size": {
			"size": "1024x1024",
		},
		"resolution": {
			"resolution": "2160x1566",
		},
	} {
		t.Run(name, func(t *testing.T) {
			filtered, summary, err := filterRuntimeCandidatesByRequest("images.generations", "gpt-image-1", "image_generate", body, candidates)
			if err != nil {
				t.Fatalf("pixel size compatibility should skip resolution filtering: %v", err)
			}
			if len(filtered) != 1 || summary != nil {
				t.Fatalf("expected unchanged candidates and no summary, got filtered=%+v summary=%+v", filtered, summary)
			}
		})
	}
}

func TestBuildFailureResultIncludesModelCandidateDetails(t *testing.T) {
	cause := &store.ModelCandidateUnavailableError{
		Code:    unsupportedRequestResolutionCode,
		Message: "unsupported resolution",
		Details: map[string]any{
			"requestedResolution": "4K",
			"candidateCount":      2,
		},
	}

	result := buildFailureResult(store.ModelCandidateErrorCode(cause), cause.Error(), "", cause)
	errorPayload, _ := result["error"].(map[string]any)
	modelCandidate, _ := errorPayload["modelCandidate"].(map[string]any)
	if errorPayload["code"] != unsupportedRequestResolutionCode || modelCandidate["requestedResolution"] != "4K" {
		t.Fatalf("failure result should persist candidate details, got %+v", result)
	}
}

func candidateWithResolutions(platformKey string, resolutions ...string) store.RuntimeModelCandidate {
	return store.RuntimeModelCandidate{
		PlatformID:      "platform-" + platformKey,
		PlatformKey:     platformKey,
		PlatformName:    "Platform " + platformKey,
		PlatformModelID: "model-" + platformKey,
		ModelName:       "demo-video",
		ModelType:       "video_generate",
		Capabilities: map[string]any{
			"video_generate": map[string]any{
				"output_resolutions": stringsToAny(resolutions),
			},
		},
	}
}

func candidateWithImageResolutions(platformKey string, resolutions ...string) store.RuntimeModelCandidate {
	return store.RuntimeModelCandidate{
		PlatformID:      "platform-" + platformKey,
		PlatformKey:     platformKey,
		PlatformName:    "Platform " + platformKey,
		PlatformModelID: "model-" + platformKey,
		ModelName:       "demo-image",
		ModelType:       "image_generate",
		Capabilities: map[string]any{
			"image_generate": map[string]any{
				"output_resolutions": stringsToAny(resolutions),
			},
		},
	}
}

func stringsToAny(values []string) []any {
	out := make([]any, 0, len(values))
	for _, value := range values {
		out = append(out, value)
	}
	return out
}
