package clients

import (
	"context"
	"fmt"
	"math/rand"
	"strings"
	"time"
)

type SimulationClient struct{}

const (
	defaultSimulationTextMinDuration  = 800 * time.Millisecond
	defaultSimulationTextMaxDuration  = 2400 * time.Millisecond
	defaultSimulationImageMinDuration = 10 * time.Second
	defaultSimulationImageMaxDuration = 30 * time.Second
	defaultSimulationAudioMinDuration = 2 * time.Second
	defaultSimulationAudioMaxDuration = 6 * time.Second
	defaultSimulationVideoMinDuration = 2 * time.Minute
	defaultSimulationVideoMaxDuration = 3 * time.Minute
	maxSimulationDuration             = 10 * time.Minute
)

func (c SimulationClient) Run(ctx context.Context, request Request) (Response, error) {
	profile := simulationProfile(request)
	responseStartedAt := time.Now()
	duration := simulationDuration(request)
	if duration > 0 {
		timer := time.NewTimer(duration)
		select {
		case <-ctx.Done():
			timer.Stop()
			return Response{}, ctx.Err()
		case <-timer.C:
		}
	}
	responseFinishedAt := time.Now()
	switch profile {
	case "retryable_failure":
		return Response{}, &ClientError{
			Code:               "server_error",
			Message:            "simulated retryable failure",
			RequestID:          "simulated-request",
			ResponseStartedAt:  responseStartedAt,
			ResponseFinishedAt: responseFinishedAt,
			ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
			Retryable:          true,
		}
	case "rate_limit", "overloaded":
		return Response{}, &ClientError{
			Code:               profile,
			Message:            "simulated " + profile,
			RequestID:          "simulated-request",
			ResponseStartedAt:  responseStartedAt,
			ResponseFinishedAt: responseFinishedAt,
			ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
			Retryable:          true,
		}
	case "invalid_api_key":
		return Response{}, &ClientError{
			Code:               "invalid_api_key",
			Message:            "simulated invalid_api_key",
			RequestID:          "simulated-request",
			ResponseStartedAt:  responseStartedAt,
			ResponseFinishedAt: responseFinishedAt,
			ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
			Retryable:          false,
		}
	case "fatal_failure", "non_retryable_failure":
		return Response{}, &ClientError{
			Code:               "bad_request",
			Message:            "simulated non-retryable failure",
			RequestID:          "simulated-request",
			ResponseStartedAt:  responseStartedAt,
			ResponseFinishedAt: responseFinishedAt,
			ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
			Retryable:          false,
		}
	}
	result := simulatedResult(request)
	return Response{
		Result:             result,
		RequestID:          requestIDFromResult(result),
		Usage:              simulatedUsage(request),
		Progress:           simulatedProgress(request),
		ResponseStartedAt:  responseStartedAt,
		ResponseFinishedAt: responseFinishedAt,
		ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
	}, nil
}

func simulationProfile(request Request) string {
	if value := stringValue(request.Candidate.Credentials, "simulationFailure"); value != "" {
		return value
	}
	if value := stringValue(request.Candidate.PlatformConfig, "simulationFailure"); value != "" {
		return value
	}
	if value := stringValue(request.Body, "simulationProfile"); value != "" {
		return value
	}
	if value := stringValue(request.Body, "testProfile"); value != "" {
		return value
	}
	return "success"
}

func simulatedResult(request Request) map[string]any {
	usage := simulatedUsage(request)
	switch request.Kind {
	case "chat.completions":
		return map[string]any{
			"id":      "chatcmpl-simulated",
			"object":  "chat.completion",
			"created": nowUnix(),
			"model":   request.Model,
			"choices": []any{map[string]any{
				"index":         0,
				"finish_reason": "stop",
				"message": map[string]any{
					"role":    "assistant",
					"content": fmt.Sprintf("simulation response from %s", request.Candidate.Provider),
				},
			}},
			"usage": simulatedOpenAIUsageMap(usage),
		}
	case "responses":
		return map[string]any{
			"id":          "resp-simulated",
			"object":      "response",
			"created_at":  nowUnix(),
			"model":       request.Model,
			"output_text": fmt.Sprintf("simulation response from %s", request.Candidate.Provider),
			"usage":       simulatedResponseUsageMap(usage),
		}
	case "embeddings":
		return simulatedEmbeddingResult(request)
	case "reranks":
		return simulatedRerankResult(request)
	case "images.edits":
		return map[string]any{
			"id":      "img-edit-simulated",
			"created": nowUnix(),
			"model":   request.Model,
			"data":    simulatedImageData(request, "/static/simulation/image-edit.svg", "simulation image edit"),
		}
	case "images.generations":
		return map[string]any{
			"id":      "img-simulated",
			"created": nowUnix(),
			"model":   request.Model,
			"data":    simulatedImageData(request, "/static/simulation/image.svg", "simulation image"),
		}
	case "videos.generations":
		return map[string]any{
			"id":      "video-simulated",
			"created": nowUnix(),
			"model":   request.Model,
			"data":    simulatedVideoData(request),
		}
	case "song.generations", "music.generations":
		return map[string]any{
			"id":      "song-simulated",
			"created": nowUnix(),
			"model":   request.Model,
			"status":  "success",
			"data":    simulatedAudioData(request, "simulation music"),
			"message": "simulation music generated",
		}
	case "speech.generations":
		return map[string]any{
			"id":      "speech-simulated",
			"created": nowUnix(),
			"model":   request.Model,
			"status":  "success",
			"data":    simulatedAudioData(request, "simulation speech"),
			"message": "simulation speech generated",
		}
	case "voice.clone":
		voiceID := strings.TrimSpace(stringValue(request.Body, "voice_id"))
		if voiceID == "" {
			voiceID = "SimVoice001"
		}
		return map[string]any{
			"id":          "voice-clone-simulated",
			"created":     nowUnix(),
			"model":       request.Model,
			"status":      "success",
			"voice_id":    voiceID,
			"demo_audio":  "/static/simulation/audio.wav",
			"data":        []any{map[string]any{"type": "audio", "url": "/static/simulation/audio.wav", "assetSource": "simulation"}},
			"message":     "simulation voice cloned",
			"base_resp":   map[string]any{"status_code": 0, "status_msg": "success"},
			"extra_info":  map[string]any{"similarity": 1},
			"input_check": map[string]any{"input_sensitive": false},
		}
	default:
		modelType := strings.ToLower(request.ModelType)
		kind := strings.ToLower(request.Kind)
		if strings.Contains(modelType, "video") || strings.Contains(kind, "video") {
			return map[string]any{
				"id":      "video-simulated",
				"created": nowUnix(),
				"model":   request.Model,
				"data":    simulatedVideoData(request),
			}
		}
		if strings.Contains(modelType, "audio") || strings.Contains(modelType, "speech") || strings.Contains(kind, "audio") || strings.Contains(kind, "song") || strings.Contains(kind, "music") || strings.Contains(kind, "speech") {
			return map[string]any{
				"id":      "audio-simulated",
				"created": nowUnix(),
				"model":   request.Model,
				"status":  "success",
				"data":    simulatedAudioData(request, "simulation audio"),
			}
		}
		return map[string]any{
			"id":      "img-simulated",
			"created": nowUnix(),
			"model":   request.Model,
			"data":    simulatedImageData(request, "/static/simulation/image.svg", "simulation image"),
		}
	}
}

func simulatedEmbeddingResult(request Request) map[string]any {
	inputCount := simulatedEmbeddingInputCount(request.Body["input"])
	dimensions := intValue(request.Body, "dimensions", 3)
	if dimensions <= 0 {
		dimensions = 3
	}
	if dimensions > 2048 {
		dimensions = 2048
	}
	data := make([]any, 0, inputCount)
	for index := 0; index < inputCount; index += 1 {
		embedding := make([]any, 0, dimensions)
		for dimension := 0; dimension < dimensions; dimension += 1 {
			embedding = append(embedding, float64(index+1)/10+float64(dimension)/100)
		}
		data = append(data, map[string]any{
			"object":    "embedding",
			"index":     index,
			"embedding": embedding,
		})
	}
	usage := simulatedUsage(request)
	return map[string]any{
		"id":     "embd-simulated",
		"object": "list",
		"model":  request.Model,
		"data":   data,
		"usage":  map[string]any{"prompt_tokens": usage.InputTokens, "total_tokens": usage.TotalTokens},
	}
}

func simulatedEmbeddingInputCount(value any) int {
	switch typed := value.(type) {
	case []any:
		if len(typed) > 0 {
			return len(typed)
		}
	case []string:
		if len(typed) > 0 {
			return len(typed)
		}
	}
	return 1
}

func simulatedRerankResult(request Request) map[string]any {
	documents := simulatedRerankDocuments(request.Body["documents"])
	topN := intValue(request.Body, "top_n", len(documents))
	if topN <= 0 || topN > len(documents) {
		topN = len(documents)
	}
	results := make([]any, 0, topN)
	for index := 0; index < topN; index += 1 {
		score := 0.95 - float64(index)*0.1
		if score < 0 {
			score = 0
		}
		result := map[string]any{
			"index":           index,
			"relevance_score": score,
		}
		if boolValue(request.Body, "return_documents") {
			result["document"] = map[string]any{"text": documents[index]}
		}
		results = append(results, result)
	}
	usage := simulatedUsage(request)
	return map[string]any{
		"id":      "rerank-simulated",
		"object":  "list",
		"model":   request.Model,
		"results": results,
		"usage":   map[string]any{"total_tokens": usage.TotalTokens},
	}
}

func simulatedRerankDocuments(value any) []string {
	switch typed := value.(type) {
	case []any:
		out := make([]string, 0, len(typed))
		for _, item := range typed {
			text := stringValue(map[string]any{"value": item}, "value")
			if text == "" {
				if record, ok := item.(map[string]any); ok {
					text = firstNonEmptyString(stringValue(record, "text"), stringValue(record, "content"))
				}
			}
			out = append(out, text)
		}
		if len(out) > 0 {
			return out
		}
	case []string:
		if len(typed) > 0 {
			return typed
		}
	}
	return []string{"simulated document"}
}

func simulatedImageData(request Request, url string, fallbackPrompt string) []any {
	count := simulatedOutputCount(request.Body)
	items := make([]any, 0, count)
	for index := 0; index < count; index += 1 {
		items = append(items, map[string]any{
			"url":            url,
			"assetSource":    "simulation",
			"index":          index,
			"revised_prompt": firstNonEmptyPrompt(request.Body, fallbackPrompt),
		})
	}
	return items
}

func simulatedVideoData(request Request) []any {
	count := simulatedOutputCount(request.Body)
	items := make([]any, 0, count)
	for index := 0; index < count; index += 1 {
		items = append(items, map[string]any{
			"url":            "/static/simulation/video.mp4",
			"video_url":      "/static/simulation/video.mp4",
			"poster":         "/static/simulation/video-poster.svg",
			"duration":       simulatedVideoDurationSeconds(request),
			"assetSource":    "simulation",
			"index":          index,
			"revised_prompt": firstNonEmptyPrompt(request.Body, "simulation video"),
		})
	}
	return items
}

func simulatedAudioData(request Request, fallbackPrompt string) []any {
	count := simulatedOutputCount(request.Body)
	items := make([]any, 0, count)
	for index := 0; index < count; index += 1 {
		items = append(items, map[string]any{
			"type":         "audio",
			"url":          "/static/simulation/audio.wav",
			"audio_url":    "/static/simulation/audio.wav",
			"duration":     simulatedAudioDurationSeconds(request),
			"assetSource":  "simulation",
			"index":        index,
			"prompt":       firstNonEmptyPrompt(request.Body, fallbackPrompt),
			"revised_text": firstNonEmptyString(stringValue(request.Body, "text"), firstNonEmptyPrompt(request.Body, fallbackPrompt)),
		})
	}
	return items
}

func simulatedUsage(request Request) Usage {
	if usage, ok := simulationUsageOverride(request); ok {
		return usage
	}
	if request.ModelType == "chat" || request.ModelType == "text_generate" || request.Kind == "responses" {
		return Usage{InputTokens: 12, OutputTokens: 8, TotalTokens: 20}
	}
	if request.ModelType == "text_embedding" || request.ModelType == "text_rerank" || request.Kind == "embeddings" || request.Kind == "reranks" {
		return Usage{InputTokens: 16, TotalTokens: 16}
	}
	return Usage{}
}

func simulationUsageOverride(request Request) (Usage, bool) {
	raw, _ := request.Body["simulationUsage"].(map[string]any)
	if len(raw) == 0 {
		raw, _ = request.Body["testUsage"].(map[string]any)
	}
	if len(raw) == 0 {
		return Usage{}, false
	}
	usage := Usage{
		InputTokens:       intFromAny(firstPresent(raw["inputTokens"], raw["input_tokens"], raw["promptTokens"], raw["prompt_tokens"])),
		OutputTokens:      intFromAny(firstPresent(raw["outputTokens"], raw["output_tokens"], raw["completionTokens"], raw["completion_tokens"])),
		CachedInputTokens: intFromAny(firstPresent(raw["cachedInputTokens"], raw["cached_input_tokens"], raw["cachedPromptTokens"], raw["cached_tokens"])),
		TotalTokens:       intFromAny(firstPresent(raw["totalTokens"], raw["total_tokens"])),
	}
	if _, ok := firstPresentValue(raw["cachedInputTokens"], raw["cached_input_tokens"], raw["cachedPromptTokens"], raw["cached_tokens"]); ok {
		usage.CachedInputTokensKnown = true
	}
	if usage.TotalTokens == 0 {
		usage.TotalTokens = usage.InputTokens + usage.OutputTokens
	}
	if usage.CachedInputTokens > usage.InputTokens && usage.InputTokens > 0 {
		usage.CachedInputTokens = usage.InputTokens
	}
	return usage, true
}

func simulatedOpenAIUsageMap(usage Usage) map[string]any {
	out := map[string]any{
		"prompt_tokens":     usage.InputTokens,
		"completion_tokens": usage.OutputTokens,
		"total_tokens":      usage.TotalTokens,
	}
	if usage.CachedInputTokensKnown {
		out["prompt_tokens_details"] = map[string]any{"cached_tokens": usage.CachedInputTokens}
	}
	return out
}

func simulatedResponseUsageMap(usage Usage) map[string]any {
	out := map[string]any{
		"input_tokens":  usage.InputTokens,
		"output_tokens": usage.OutputTokens,
		"total_tokens":  usage.TotalTokens,
	}
	if usage.CachedInputTokensKnown {
		out["input_tokens_details"] = map[string]any{"cached_tokens": usage.CachedInputTokens}
	}
	return out
}

func simulatedProgress(request Request) []Progress {
	provider := request.Candidate.Provider
	if provider == "" {
		provider = "simulation"
	}
	return []Progress{
		{Phase: "normalizing", Progress: 0.2, Message: "request normalized", Payload: map[string]any{"provider": provider}},
		{Phase: "submitting", Progress: 0.55, Message: "simulation client submitted", Payload: map[string]any{"clientId": request.Candidate.ClientID}},
		{Phase: "fetching_result", Progress: 0.85, Message: "simulation result ready", Payload: map[string]any{"kind": request.Kind}},
	}
}

func simulationDuration(request Request) time.Duration {
	if fixedMS := simulationDurationMS(request, "simulationDurationMs", "testDurationMs"); fixedMS >= 0 {
		return clampSimulationDuration(time.Duration(fixedMS) * time.Millisecond)
	}
	if fixedSeconds := simulationDurationSeconds(request, "simulationDurationSeconds", "testDurationSeconds"); fixedSeconds >= 0 {
		return clampSimulationDuration(time.Duration(fixedSeconds) * time.Second)
	}
	minDuration, maxDuration := defaultSimulationDurationRange(request)
	if minMS := simulationDurationMS(request, "simulationMinDurationMs", "simulationDurationMinMs", "testMinDurationMs", "testDurationMinMs"); minMS >= 0 {
		minDuration = time.Duration(minMS) * time.Millisecond
	}
	if maxMS := simulationDurationMS(request, "simulationMaxDurationMs", "simulationDurationMaxMs", "testMaxDurationMs", "testDurationMaxMs"); maxMS >= 0 {
		maxDuration = time.Duration(maxMS) * time.Millisecond
	}
	if minSeconds := simulationDurationSeconds(request, "simulationMinDurationSeconds", "simulationDurationMinSeconds", "testMinDurationSeconds", "testDurationMinSeconds"); minSeconds >= 0 {
		minDuration = time.Duration(minSeconds) * time.Second
	}
	if maxSeconds := simulationDurationSeconds(request, "simulationMaxDurationSeconds", "simulationDurationMaxSeconds", "testMaxDurationSeconds", "testDurationMaxSeconds"); maxSeconds >= 0 {
		maxDuration = time.Duration(maxSeconds) * time.Second
	}
	minDuration = clampSimulationDuration(minDuration)
	maxDuration = clampSimulationDuration(maxDuration)
	if maxDuration < minDuration {
		maxDuration = minDuration
	}
	spread := maxDuration - minDuration
	if spread <= 0 {
		return minDuration
	}
	return minDuration + time.Duration(rand.Int63n(int64(spread)+1))
}

func defaultSimulationDurationRange(request Request) (time.Duration, time.Duration) {
	if simulationVideoRequest(request) {
		return defaultSimulationVideoMinDuration, defaultSimulationVideoMaxDuration
	}
	if simulationImageRequest(request) {
		return defaultSimulationImageMinDuration, defaultSimulationImageMaxDuration
	}
	if simulationAudioRequest(request) {
		return defaultSimulationAudioMinDuration, defaultSimulationAudioMaxDuration
	}
	return defaultSimulationTextMinDuration, defaultSimulationTextMaxDuration
}

func simulationVideoRequest(request Request) bool {
	kind := strings.ToLower(request.Kind)
	modelType := strings.ToLower(request.ModelType)
	return strings.Contains(kind, "video") || strings.Contains(modelType, "video")
}

func simulationImageRequest(request Request) bool {
	kind := strings.ToLower(request.Kind)
	modelType := strings.ToLower(request.ModelType)
	return strings.Contains(kind, "image") || strings.Contains(modelType, "image")
}

func simulationAudioRequest(request Request) bool {
	kind := strings.ToLower(request.Kind)
	modelType := strings.ToLower(request.ModelType)
	return strings.Contains(kind, "audio") || strings.Contains(kind, "song") || strings.Contains(kind, "music") || strings.Contains(kind, "speech") || strings.Contains(modelType, "audio") || strings.Contains(modelType, "speech")
}

func simulationDurationSeconds(request Request, keys ...string) int {
	for _, source := range []map[string]any{request.Body, request.Candidate.PlatformConfig, request.Candidate.Credentials} {
		for _, key := range keys {
			value := intValue(source, key, -1)
			if value >= 0 {
				return value
			}
		}
	}
	return -1
}

func simulationDurationMS(request Request, keys ...string) int {
	for _, source := range []map[string]any{request.Body, request.Candidate.PlatformConfig, request.Candidate.Credentials} {
		for _, key := range keys {
			value := intValue(source, key, -1)
			if value >= 0 {
				return value
			}
		}
	}
	return -1
}

func clampSimulationDuration(duration time.Duration) time.Duration {
	if duration < 0 {
		return 0
	}
	if duration > maxSimulationDuration {
		return maxSimulationDuration
	}
	return duration
}

func simulatedOutputCount(body map[string]any) int {
	count := intValue(body, "n", 0)
	if count <= 0 {
		count = intValue(body, "count", 0)
	}
	if count <= 0 {
		return 1
	}
	if count > 20 {
		return 20
	}
	return count
}

func simulatedVideoDurationSeconds(request Request) int {
	for _, key := range []string{"duration", "duration_seconds", "durationSeconds"} {
		if value := intValue(request.Body, key, 0); value > 0 {
			return value
		}
	}
	return 5
}

func simulatedAudioDurationSeconds(request Request) int {
	if duration := intValue(request.Body, "duration", 0); duration > 0 {
		return duration
	}
	if seconds := len([]rune(stringValue(request.Body, "text"))) / 8; seconds > 0 {
		return seconds
	}
	return 3
}

func firstNonEmptyPrompt(body map[string]any, fallback string) string {
	for _, key := range []string{"prompt", "input"} {
		if value := strings.TrimSpace(stringValue(body, key)); value != "" {
			return value
		}
	}
	for _, item := range contentItems(body["content"]) {
		if stringValue(item, "type") == "text" {
			if value := strings.TrimSpace(stringValue(item, "text")); value != "" {
				return value
			}
		}
	}
	return fallback
}
