package clients

import (
	"net/http"
	"net/url"
	"strings"

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

var openAIChatAudioFormatByMIME = map[string]string{
	"audio/aac":   "aac",
	"audio/flac":  "flac",
	"audio/m4a":   "m4a",
	"audio/mp4":   "m4a",
	"audio/mpeg":  "mp3",
	"audio/ogg":   "ogg",
	"audio/opus":  "opus",
	"audio/wav":   "wav",
	"audio/webm":  "webm",
	"audio/x-m4a": "m4a",
	"audio/x-wav": "wav",
}

var openAIChatAudioFormatByExtension = map[string]string{
	"aac":  "aac",
	"flac": "flac",
	"m4a":  "m4a",
	"mp3":  "mp3",
	"mp4":  "m4a",
	"oga":  "ogg",
	"ogg":  "ogg",
	"opus": "opus",
	"wav":  "wav",
	"webm": "webm",
}

func normalizeOpenAIChatAudioParts(body map[string]any, candidate store.RuntimeModelCandidate) (map[string]any, error) {
	if !isAliyunBailianOpenAI(candidate) {
		return body, nil
	}
	messages, ok := body["messages"].([]any)
	if !ok {
		return body, nil
	}

	normalizedMessages := make([]any, 0, len(messages))
	for _, rawMessage := range messages {
		message, ok := rawMessage.(map[string]any)
		if !ok {
			normalizedMessages = append(normalizedMessages, rawMessage)
			continue
		}
		content, ok := message["content"].([]any)
		if !ok {
			normalizedMessages = append(normalizedMessages, rawMessage)
			continue
		}

		normalizedContent := make([]any, 0, len(content))
		for _, rawPart := range content {
			part, ok := rawPart.(map[string]any)
			if !ok {
				normalizedContent = append(normalizedContent, rawPart)
				continue
			}
			normalizedPart, err := normalizeAliyunOpenAIChatAudioPart(part)
			if err != nil {
				return nil, err
			}
			normalizedContent = append(normalizedContent, normalizedPart)
		}
		copiedMessage := cloneMapAny(message)
		copiedMessage["content"] = normalizedContent
		normalizedMessages = append(normalizedMessages, copiedMessage)
	}

	out := cloneMapAny(body)
	out["messages"] = normalizedMessages
	return out, nil
}

func normalizeAliyunOpenAIChatAudioPart(part map[string]any) (map[string]any, error) {
	switch strings.TrimSpace(stringFromAny(part["type"])) {
	case "audio_url":
		audioURL, _ := part["audio_url"].(map[string]any)
		data := strings.TrimSpace(stringFromAny(audioURL["url"]))
		format := resolveOpenAIChatAudioFormat(data, audioURL["format"], audioURL["mime_type"], audioURL["mimeType"])
		if data == "" || format == "" {
			return nil, invalidOpenAIChatAudioError()
		}
		return map[string]any{
			"type": "input_audio",
			"input_audio": map[string]any{
				"data":   data,
				"format": format,
			},
		}, nil
	case "input_audio":
		inputAudio, _ := part["input_audio"].(map[string]any)
		data := firstNonEmptyString(inputAudio["data"], inputAudio["url"])
		format := resolveOpenAIChatAudioFormat(data, inputAudio["format"], inputAudio["mime_type"], inputAudio["mimeType"])
		if data == "" || format == "" {
			return nil, invalidOpenAIChatAudioError()
		}
		copiedInput := cloneMapAny(inputAudio)
		copiedInput["data"] = data
		copiedInput["format"] = format
		delete(copiedInput, "url")
		copiedPart := cloneMapAny(part)
		copiedPart["input_audio"] = copiedInput
		return copiedPart, nil
	default:
		return part, nil
	}
}

func resolveOpenAIChatAudioFormat(source string, values ...any) string {
	if explicit := strings.ToLower(strings.TrimSpace(firstNonEmptyString(values...))); explicit != "" {
		if normalized := openAIChatAudioFormatByExtension[explicit]; normalized != "" {
			return normalized
		}
		if normalized := openAIChatAudioFormatByMIME[strings.TrimSpace(strings.Split(explicit, ";")[0])]; normalized != "" {
			return normalized
		}
		return explicit
	}
	if dataMIME := openAIChatAudioDataMIME(source); dataMIME != "" {
		if normalized := openAIChatAudioFormatByMIME[dataMIME]; normalized != "" {
			return normalized
		}
	}
	if extension := openAIChatAudioSourceExtension(source); extension != "" {
		return openAIChatAudioFormatByExtension[extension]
	}
	return ""
}

func openAIChatAudioDataMIME(source string) string {
	if !strings.HasPrefix(strings.ToLower(source), "data:") {
		return ""
	}
	header := strings.TrimPrefix(source[:strings.Index(source+",", ",")], "data:")
	return strings.ToLower(strings.TrimSpace(strings.Split(header, ";")[0]))
}

func openAIChatAudioSourceExtension(source string) string {
	if source == "" || strings.HasPrefix(strings.ToLower(source), "data:") {
		return ""
	}
	if parsed, err := url.Parse(source); err == nil && parsed.Path != "" {
		source = parsed.Path
	}
	source = strings.TrimSuffix(strings.Split(strings.Split(source, "?")[0], "#")[0], "/")
	if index := strings.LastIndex(source, "."); index >= 0 && index+1 < len(source) {
		return strings.ToLower(source[index+1:])
	}
	return ""
}

func invalidOpenAIChatAudioError() error {
	return &ClientError{
		Code:       "invalid_parameter",
		Message:    "input_audio requires data and a resolvable format",
		Param:      "messages.content.input_audio",
		StatusCode: http.StatusBadRequest,
		Retryable:  false,
	}
}
