package runner

import (
	"context"
	"testing"

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

func TestImageBillingEstimateUsesCountResolutionAndQuality(t *testing.T) {
	service := &Service{}
	candidate := store.RuntimeModelCandidate{
		ModelName: "image-model",
		BaseBillingConfig: map[string]any{
			"image": map[string]any{
				"basePrice": 10,
				"dynamicWeight": map[string]any{
					"resolutionFactors": map[string]any{"2K": 1.5},
					"qualityFactors":    map[string]any{"high": 1.5},
				},
			},
		},
	}

	items := service.billings(context.Background(), nil, "images.generations", map[string]any{
		"count":      2,
		"quality":    "high",
		"resolution": "2K",
	}, candidate, clients.Response{}, true)

	line := firstBillingLine(t, items)
	if got, want := floatFromAny(line["amount"]), 45.0; got != want {
		t.Fatalf("image estimated amount = %v, want %v", got, want)
	}
	if got, want := line["quantity"], 2; got != want {
		t.Fatalf("image quantity = %v, want %v", got, want)
	}
}

func TestVideoBillingEstimateProratesFiveSecondUnitsAndDynamicWeights(t *testing.T) {
	service := &Service{}
	candidate := store.RuntimeModelCandidate{
		ModelName: "video-model",
		BaseBillingConfig: map[string]any{
			"video": map[string]any{
				"basePrice": 100,
				"dynamicWeight": map[string]any{
					"resolutionWeights":        map[string]any{"720p": 1.25, "1080p": 1.5},
					"audioWeights":             map[string]any{"true": 2},
					"referenceVideoWeights":    map[string]any{"true": 1.5},
					"voiceSpecifiedWeights":    map[string]any{"true": 1.2},
					"unusedCompatibilityField": map[string]any{"true": 99},
				},
			},
		},
	}

	items := service.billings(context.Background(), nil, "videos.generations", map[string]any{
		"audio":      true,
		"duration":   12,
		"resolution": "1080P",
		"voice_id":   "voice-a",
		"content": []any{
			map[string]any{"type": "video_url", "video_url": map[string]any{"url": "https://example.com/reference.mp4"}},
		},
	}, candidate, clients.Response{}, true)

	line := firstBillingLine(t, items)
	if got, want := floatFromAny(line["amount"]), 1296.0; got != want {
		t.Fatalf("video estimated amount = %v, want %v", got, want)
	}
	if got, want := floatFromAny(line["durationUnitCount"]), 2.4; got != want {
		t.Fatalf("video duration units = %v, want %v", got, want)
	}
	if got, want := floatFromAny(line["quantity"]), 2.4; got != want {
		t.Fatalf("video quantity = %v, want %v", got, want)
	}
	if got, want := line["durationSource"], "preprocessed_request"; got != want {
		t.Fatalf("video duration source = %v, want %v", got, want)
	}
	if got, want := line["audioSource"], "preprocessed_request"; got != want {
		t.Fatalf("video audio source = %v, want %v", got, want)
	}

	items = service.billings(context.Background(), nil, "videos.generations", map[string]any{
		"duration":   5,
		"resolution": "768P",
	}, candidate, clients.Response{}, true)
	line = firstBillingLine(t, items)
	if got, want := floatFromAny(line["amount"]), 125.0; got != want {
		t.Fatalf("768P should use 720p billing weight, got %v, want %v", got, want)
	}
}

func TestAdvancedMediaBillingUsesConversionAndTransitionMatrix(t *testing.T) {
	service := &Service{}
	vectorCandidate := store.RuntimeModelCandidate{ModelName: "easy-image-vectorizer-1", BaseBillingConfig: map[string]any{
		"image_vectorize": map[string]any{"basePrice": 200},
	}}
	vectorLine := firstBillingLine(t, service.billings(context.Background(), nil, "images.vectorize", map[string]any{"count": 1}, vectorCandidate, clients.Response{}, true))
	if vectorLine["resourceType"] != "image_vectorize" || vectorLine["unit"] != "conversion" || floatFromAny(vectorLine["amount"]) != 200 {
		t.Fatalf("unexpected vectorizer billing: %+v", vectorLine)
	}

	topazCandidate := store.RuntimeModelCandidate{ModelName: "easy-proteus-standard-4", BaseBillingConfig: map[string]any{
		"video_enhance": map[string]any{
			"basePrice": 100,
			"dynamicWeight": map[string]any{
				"resolutionTransitions": map[string]any{"720p->1080p": 1.8},
				"frameRateTransitions":  map[string]any{"24->60": 2.5},
				"markup":                1,
			},
		},
	}}
	topazLine := firstBillingLine(t, service.billings(context.Background(), nil, "videos.upscales", map[string]any{
		"count": 1, "duration": 5, "source_resolution": "720p", "target_resolution": "1080p",
		"source_frame_rate": 24, "target_frame_rate": 60, "slow_motion_rate": 2,
		"model": "easy-proteus-standard-4",
	}, topazCandidate, clients.Response{}, true))
	if got, want := floatFromAny(topazLine["amount"]), 900.0; got != want {
		t.Fatalf("video enhance amount=%v, want=%v, line=%+v", got, want, topazLine)
	}
	if topazLine["resolutionTransitionKey"] != "720p->1080p" || topazLine["frameRateTransitionKey"] != "24->60" {
		t.Fatalf("missing transition evidence: %+v", topazLine)
	}
}

func TestVideoEnhancePricingFallbackUsesPixelsAndBaseline(t *testing.T) {
	amount, details := videoEnhanceAmount(map[string]any{"video_enhance": map[string]any{"dynamicWeight": map[string]any{}}}, videoEnhancePricingInputs{
		DurationSeconds: 5, DurationUnits: 1, SourceResolution: "720p", TargetResolution: "2160p",
		SourceFrameRate: 30, TargetFrameRate: 60, SlowMotionRate: 1,
	}, 1, 100)
	if amount != 2250 {
		t.Fatalf("fallback amount=%v, want=2250 details=%+v", amount, details)
	}
}

func TestVideoEnhancePricingPrefersProbedDimensions(t *testing.T) {
	inputs := resolveVideoEnhancePricingInputs(map[string]any{
		"duration":          3,
		"source_resolution": "180p",
		"source_width":      320,
		"source_height":     180,
		"target_resolution": "720p",
		"output_width":      1280,
		"output_height":     720,
	}, clients.Response{})
	if inputs.SourceResolution != "480p" || inputs.TargetResolution != "720p" {
		t.Fatalf("dimension buckets = %s->%s, want 480p->720p", inputs.SourceResolution, inputs.TargetResolution)
	}
}

func TestMusicBillingUsesSongResourceAndOutputCount(t *testing.T) {
	service := &Service{}
	candidate := store.RuntimeModelCandidate{
		ModelName: "suno-model",
		BaseBillingConfig: map[string]any{
			"musicBase": 6,
			"music":     map[string]any{"basePrice": 9},
		},
	}

	items := service.billings(context.Background(), nil, "song.generations", map[string]any{
		"prompt": "city lights",
		"count":  3,
	}, candidate, clients.Response{}, true)

	line := firstBillingLine(t, items)
	if got, want := line["resourceType"], "music"; got != want {
		t.Fatalf("music resource type = %v, want %v", got, want)
	}
	if got, want := line["unit"], "song"; got != want {
		t.Fatalf("music billing unit = %v, want %v", got, want)
	}
	if got, want := line["quantity"], 3; got != want {
		t.Fatalf("music quantity = %v, want %v", got, want)
	}
	if got, want := floatFromAny(line["amount"]), 18.0; got != want {
		t.Fatalf("music amount = %v, want %v", got, want)
	}
}

func TestSpeechBillingUsesAudioCharacters(t *testing.T) {
	service := &Service{}
	candidate := store.RuntimeModelCandidate{
		ModelName: "speech-model",
		BaseBillingConfig: map[string]any{
			"audioBase": 0.5,
			"audio":     map[string]any{"basePrice": 0.8},
		},
	}

	items := service.billings(context.Background(), nil, "speech.generations", map[string]any{
		"text":     "你好abc",
		"voice_id": "female-shaonv",
	}, candidate, clients.Response{}, true)

	line := firstBillingLine(t, items)
	if got, want := line["resourceType"], "audio"; got != want {
		t.Fatalf("speech resource type = %v, want %v", got, want)
	}
	if got, want := line["unit"], "character"; got != want {
		t.Fatalf("speech billing unit = %v, want %v", got, want)
	}
	if got, want := line["quantity"], 5; got != want {
		t.Fatalf("speech character quantity = %v, want %v", got, want)
	}
	if got, want := floatFromAny(line["amount"]), 2.5; got != want {
		t.Fatalf("speech amount = %v, want %v", got, want)
	}
}

func TestVideoBillingPrefersGeneratedDuration(t *testing.T) {
	service := &Service{}
	candidate := store.RuntimeModelCandidate{
		ModelName: "video-model",
		BaseBillingConfig: map[string]any{
			"video": map[string]any{"basePrice": 100},
		},
	}

	items := service.billings(context.Background(), nil, "videos.generations", map[string]any{
		"duration":   12,
		"resolution": "720p",
	}, candidate, clients.Response{
		Result: map[string]any{
			"data": []any{map[string]any{"type": "video", "duration": 6.6}},
		},
	}, false)

	line := firstBillingLine(t, items)
	if got, want := floatFromAny(line["durationSeconds"]), 6.6; got != want {
		t.Fatalf("video generated duration = %v, want %v", got, want)
	}
	if got, want := floatFromAny(line["durationUnitCount"]), 1.32; got != want {
		t.Fatalf("video generated duration units = %v, want %v", got, want)
	}
	if got, want := floatFromAny(line["amount"]), 132.0; got != want {
		t.Fatalf("video generated duration amount = %v, want %v", got, want)
	}
	if got, want := line["durationSource"], "generated_video"; got != want {
		t.Fatalf("video duration source = %v, want %v", got, want)
	}
}

func TestVideoBillingPrefersGeneratedAudio(t *testing.T) {
	service := &Service{}
	candidate := store.RuntimeModelCandidate{
		ModelName: "video-model",
		BaseBillingConfig: map[string]any{
			"video": map[string]any{
				"basePrice":             100,
				"audioWeights":          map[string]any{"true": 2, "false": 0.5},
				"voiceSpecifiedWeights": map[string]any{"true": 4},
			},
		},
	}

	items := service.billings(context.Background(), nil, "videos.generations", map[string]any{
		"audio":    false,
		"duration": 5,
	}, candidate, clients.Response{
		Result: map[string]any{
			"data": []any{map[string]any{"type": "video", "has_audio": true}},
		},
	}, false)

	line := firstBillingLine(t, items)
	if got, want := floatFromAny(line["amount"]), 200.0; got != want {
		t.Fatalf("video generated audio amount = %v, want %v", got, want)
	}
	if got, want := line["audio"], true; got != want {
		t.Fatalf("video generated audio = %v, want %v", got, want)
	}
	if got, want := line["audioSource"], "generated_video"; got != want {
		t.Fatalf("video audio source = %v, want %v", got, want)
	}

	items = service.billings(context.Background(), nil, "videos.generations", map[string]any{
		"audio":    true,
		"duration": 5,
		"voice_id": "voice-a",
	}, candidate, clients.Response{
		Result: map[string]any{
			"data": []any{map[string]any{"type": "video", "hasAudio": false}},
		},
	}, false)

	line = firstBillingLine(t, items)
	if got, want := floatFromAny(line["amount"]), 50.0; got != want {
		t.Fatalf("video generated no-audio amount = %v, want %v", got, want)
	}
	if got, want := line["audio"], false; got != want {
		t.Fatalf("video generated no-audio = %v, want %v", got, want)
	}
	if got, want := line["audioSource"], "generated_video"; got != want {
		t.Fatalf("video no-audio source = %v, want %v", got, want)
	}
}

func TestVideoBillingEstimateSupportsServerMainStyleDynamicKeys(t *testing.T) {
	service := &Service{}
	candidate := store.RuntimeModelCandidate{
		ModelName: "legacy-video-model",
		BaseBillingConfig: map[string]any{
			"videoBase": 100,
			"video": map[string]any{
				"dynamicWeight": map[string]any{
					"720p":                 1.25,
					"audio-true":           2,
					"reference-video-true": 1.5,
				},
			},
		},
	}

	items := service.billings(context.Background(), nil, "videos.generations", map[string]any{
		"audio":      "true",
		"duration":   5,
		"resolution": "720p",
		"video_list": []any{map[string]any{"url": "https://example.com/reference.mp4"}},
	}, candidate, clients.Response{}, true)

	line := firstBillingLine(t, items)
	if got, want := floatFromAny(line["amount"]), 375.0; got != want {
		t.Fatalf("legacy video estimated amount = %v, want %v", got, want)
	}
}

func TestVideoDurationEstimateSumsMultiShotDurations(t *testing.T) {
	duration := requestDurationSeconds(map[string]any{
		"multi_prompt": []any{
			map[string]any{"prompt": "shot 1", "duration": 3},
			map[string]any{"prompt": "shot 2", "duration": 7},
		},
	})
	if duration != 10 {
		t.Fatalf("multi-shot duration = %v, want 10", duration)
	}
}

func firstBillingLine(t *testing.T, items []any) map[string]any {
	t.Helper()
	if len(items) != 1 {
		t.Fatalf("items length = %d, want 1: %+v", len(items), items)
	}
	line, ok := items[0].(map[string]any)
	if !ok {
		t.Fatalf("item type = %T, want map[string]any", items[0])
	}
	return line
}
