package httpapi

import (
	"net/http"
	"strings"

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

func easyAIAsyncMediaRequest(kind string, r *http.Request) bool {
	if r == nil || !asyncRequest(r) {
		return false
	}
	switch kind {
	case "images.generations",
		"images.edits",
		"images.vectorize",
		"videos.generations",
		"videos.upscales",
		"song.generations",
		"music.generations",
		"speech.generations",
		"voice.clone":
		return true
	default:
		return false
	}
}

func easyAITaskAcceptedResponse(task store.GatewayTask) map[string]any {
	resultPath := "/api/v1/ai/result/" + task.ID
	return map[string]any{
		"status":    "submitted",
		"task_id":   task.ID,
		"taskId":    task.ID,
		"query_url": resultPath,
		"task":      task,
		"next": map[string]string{
			"events": "/api/v1/tasks/" + task.ID + "/events",
			"detail": "/api/v1/tasks/" + task.ID,
			"result": resultPath,
		},
	}
}

func easyAITaskAcceptedResponseWithExtensions(task store.GatewayTask, extensions map[string]any) map[string]any {
	response := cloneEasyAIMap(extensions)
	for key, value := range easyAITaskAcceptedResponse(task) {
		response[key] = value
	}
	return response
}

func writeEasyAIAsyncError(w http.ResponseWriter, status int, message string, details map[string]any, codes ...string) {
	code := ""
	if len(codes) > 0 {
		code = strings.TrimSpace(codes[0])
	}
	standard := publicerror.FromFields(code, message, status, status == http.StatusRequestTimeout || status == http.StatusTooManyRequests || status >= 500)
	standard = publicErrorWithRetryAfter(standard, details)
	publicerror.Observe(standard)
	status, message, code = standard.HTTPStatus, standard.Message, standard.Code
	errorPayload := map[string]any{
		"message":    message,
		"status":     status,
		"code":       code,
		"category":   standard.Category,
		"source":     standard.Source,
		"httpStatus": standard.HTTPStatus,
		"retryable":  standard.Retryable,
		"action":     standard.Action,
		"version":    standard.Version,
	}
	for key, value := range safePublicErrorDetails(details, standard, false) {
		errorPayload[key] = value
	}
	response := map[string]any{
		"status":  "failed",
		"message": message,
		"data":    []any{},
		"error":   errorPayload,
	}
	if code != "" {
		response["code"] = code
	}
	writeJSON(w, status, response)
}

func easyAISynchronousTaskResponse(task store.GatewayTask, output map[string]any) map[string]any {
	if task.Kind != "images.vectorize" {
		return output
	}
	response := cloneEasyAIMap(output)
	response["taskId"] = task.ID
	response["task_id"] = task.ID
	response["query_url"] = "/api/v1/ai/result/" + task.ID
	return response
}

func easyAIFileUploadResponse(upload map[string]any) map[string]any {
	response := cloneEasyAIMap(upload)
	response["status"] = "success"
	response["data"] = cloneEasyAIMap(upload)
	return response
}

func easyAITaskResultResponse(task store.GatewayTask) map[string]any {
	sourceResult := cloneEasyAIMap(task.Result)
	data := easyAITaskResultData(task, sourceResult)
	status := easyAITaskResultStatus(task.Status)
	if status == "failed" {
		data = []any{}
	}
	output := easyAIOutputURLs(data)
	compatResult := map[string]any{
		"data":           data,
		"output":         output,
		"output_content": data,
	}
	response := map[string]any{
		"status":         status,
		"task_id":        task.ID,
		"taskId":         task.ID,
		"query_url":      "/api/v1/ai/result/" + task.ID,
		"created":        task.CreatedAt.UnixMilli(),
		"data":           data,
		"output":         output,
		"output_content": data,
		"result":         compatResult,
	}

	upstreamTaskID := firstNonEmpty(
		easyAIString(sourceResult["upstream_task_id"]),
		task.RemoteTaskID,
	)
	if upstreamTaskID != "" {
		response["upstream_task_id"] = upstreamTaskID
	}
	if len(task.Usage) > 0 && response["usage"] == nil {
		response["usage"] = task.Usage
	}

	if status != "success" {
		cancelState := runner.DescribeTaskCancellation(task)
		response["cancellable"] = cancelState.Cancellable
		response["submitted"] = cancelState.Submitted
	}

	message := firstNonEmpty(
		easyAIString(sourceResult["message"]),
		task.ErrorMessage,
		task.Error,
		task.Message,
	)
	if message == "" {
		switch status {
		case "submitted":
			message = "任务已提交，请稍后查看结果"
		case "process":
			message = "任务处理中"
		case "failed":
			message = "任务执行失败"
		}
	}
	if message != "" && status != "success" {
		response["message"] = message
	}
	if code := firstNonEmpty(task.ErrorCode, easyAIString(sourceResult["code"])); code != "" || status == "failed" {
		standard := publicTaskError(task)
		if code != "" && task.ErrorCode == "" {
			standard = publicerror.WithIDs(publicerror.FromFields(code, message, 0, false), task.RequestID, task.ID)
		}
		response["code"] = standard.Code
		response["message"] = standard.Message
		response["error"] = standard
	}
	return response
}

func easyAITaskResultStatus(status string) string {
	switch strings.ToLower(strings.TrimSpace(status)) {
	case "succeeded", "success", "completed":
		return "success"
	case "failed", "cancelled", "canceled", "rejected":
		return "failed"
	case "running", "processing", "in_progress":
		return "process"
	default:
		return "submitted"
	}
}

func easyAITaskResultData(task store.GatewayTask, result map[string]any) []any {
	for _, source := range easyAIResultSources(result) {
		if items := easyAIOutputArray(source); len(items) > 0 {
			return normalizeEasyAIOutputItems(task, items)
		}
	}
	if item := easyAIOutputMap(result); len(item) > 0 {
		normalizeEasyAIInlineMediaFields(item)
		if easyAIOutputURL(item) != "" ||
			easyAIOutputHasInlineMedia(item) ||
			(strings.EqualFold(easyAIString(item["type"]), "text") && (item["text"] != nil || item["content"] != nil)) {
			return normalizeEasyAIOutputItems(task, []any{item})
		}
	}
	return []any{}
}

func easyAIResultSources(result map[string]any) []any {
	sources := []any{
		result["data"],
		result["output_content"],
		easyAIStructuredOutput(result["content"]),
		result["images"],
		result["videos"],
		result["audios"],
		result["files"],
		result["output"],
	}
	if raw, ok := result["raw"].(map[string]any); ok {
		sources = append(sources,
			raw["data"],
			raw["output_content"],
			easyAIStructuredOutput(raw["content"]),
			raw["images"],
			raw["videos"],
			raw["audios"],
			raw["files"],
			raw["output"],
		)
	}
	return sources
}

func easyAIStructuredOutput(value any) any {
	switch value.(type) {
	case []any, []string, map[string]any:
		return value
	default:
		return nil
	}
}

func easyAIOutputArray(value any) []any {
	switch typed := value.(type) {
	case []any:
		return typed
	case []string:
		items := make([]any, 0, len(typed))
		for _, item := range typed {
			items = append(items, item)
		}
		return items
	case map[string]any, string:
		return []any{typed}
	default:
		return nil
	}
}

func normalizeEasyAIOutputItems(task store.GatewayTask, items []any) []any {
	normalized := make([]any, 0, len(items))
	for _, item := range items {
		output := easyAIOutputMap(item)
		if len(output) == 0 {
			continue
		}
		normalizeEasyAIInlineMediaFields(output)

		mediaURL := easyAIOutputURL(output)
		if mediaURL == "" {
			continue
		}
		lightweight := map[string]any{"url": mediaURL}
		for _, key := range []string{"type", "mime_type", "width", "height", "duration", "format", "seed", "revised_prompt"} {
			if value, exists := output[key]; exists && value != nil {
				lightweight[key] = value
			}
		}
		if strings.TrimSpace(easyAIString(lightweight["type"])) == "" {
			if outputType := easyAIOutputType(task, output, mediaURL); outputType != "" {
				lightweight["type"] = outputType
			}
		}
		normalized = append(normalized, lightweight)
	}
	return normalized
}

func easyAIOutputMap(value any) map[string]any {
	switch typed := value.(type) {
	case map[string]any:
		return cloneEasyAIMap(typed)
	case string:
		trimmed := strings.TrimSpace(typed)
		if trimmed == "" {
			return nil
		}
		if strings.HasPrefix(strings.ToLower(trimmed), "data:") {
			return map[string]any{"url": trimmed}
		}
		if strings.HasPrefix(trimmed, "/") ||
			strings.HasPrefix(strings.ToLower(trimmed), "http://") ||
			strings.HasPrefix(strings.ToLower(trimmed), "https://") {
			return map[string]any{"url": trimmed}
		}
		return map[string]any{"type": "text", "content": trimmed}
	default:
		return nil
	}
}

func easyAIOutputURL(item map[string]any) string {
	for _, key := range []string{
		"url",
		"generated_url",
		"generatedUrl",
		"output_url",
		"outputUrl",
		"download_url",
		"downloadUrl",
		"image_url",
		"video_url",
		"audio_url",
	} {
		if value := easyAINestedURL(item[key]); value != "" {
			return value
		}
	}
	return ""
}

func easyAINestedURL(value any) string {
	var candidate string
	switch typed := value.(type) {
	case string:
		candidate = strings.TrimSpace(typed)
	case map[string]any:
		candidate = strings.TrimSpace(easyAIString(typed["url"]))
	default:
		return ""
	}
	if strings.HasPrefix(strings.ToLower(candidate), "data:") {
		return ""
	}
	return candidate
}

func normalizeEasyAIInlineMediaFields(item map[string]any) {
	for _, key := range []string{
		"url",
		"generated_url",
		"generatedUrl",
		"output_url",
		"outputUrl",
		"download_url",
		"downloadUrl",
		"image_url",
		"video_url",
		"audio_url",
	} {
		value := easyAINestedURLAllowData(item[key])
		contentType, payload, ok := easyAIBase64DataURL(value)
		if !ok {
			continue
		}
		if strings.HasPrefix(contentType, "image/") {
			if easyAIString(item["b64_json"]) == "" {
				item["b64_json"] = payload
			}
		} else if easyAIString(item["content"]) == "" {
			item["content"] = value
		}
		if easyAIString(item["mime_type"]) == "" {
			item["mime_type"] = contentType
		}
		delete(item, key)
	}
}

func easyAIBase64DataURL(value string) (string, string, bool) {
	value = strings.TrimSpace(value)
	if !strings.HasPrefix(strings.ToLower(value), "data:") {
		return "", "", false
	}
	meta, payload, ok := strings.Cut(value, ",")
	if !ok || strings.TrimSpace(payload) == "" {
		return "", "", false
	}
	parts := strings.Split(strings.TrimSpace(meta[5:]), ";")
	if len(parts) < 2 || !strings.EqualFold(strings.TrimSpace(parts[len(parts)-1]), "base64") {
		return "", "", false
	}
	contentType := strings.ToLower(strings.TrimSpace(parts[0]))
	if !strings.HasPrefix(contentType, "image/") &&
		!strings.HasPrefix(contentType, "audio/") &&
		!strings.HasPrefix(contentType, "video/") &&
		!strings.HasPrefix(contentType, "application/") {
		return "", "", false
	}
	return contentType, strings.TrimSpace(payload), true
}

func easyAIOutputHasInlineMedia(item map[string]any) bool {
	if easyAIString(item["b64_json"]) != "" {
		return true
	}
	content := strings.ToLower(easyAIString(item["content"]))
	return strings.HasPrefix(content, "data:") && strings.Contains(content, ";base64,")
}

func easyAINestedURLAllowData(value any) string {
	switch typed := value.(type) {
	case string:
		return strings.TrimSpace(typed)
	case map[string]any:
		return strings.TrimSpace(easyAIString(typed["url"]))
	default:
		return ""
	}
}

func easyAIOutputType(task store.GatewayTask, item map[string]any, mediaURL string) string {
	kind := strings.ToLower(strings.TrimSpace(task.Kind))
	switch {
	case kind == "images.vectorize":
		format := strings.ToLower(firstNonEmpty(
			easyAIString(item["format"]),
			easyAIString(task.Request["format"]),
		))
		if format == "svg" || format == "png" {
			return "image"
		}
		if format != "" {
			return "file"
		}
	case strings.HasPrefix(kind, "images."):
		return "image"
	case strings.HasPrefix(kind, "videos.") || kind == "video.upscale":
		return "video"
	case kind == "song.generations" || kind == "music.generations" || kind == "speech.generations":
		return "audio"
	}

	lowerURL := strings.ToLower(mediaURL)
	if index := strings.IndexAny(lowerURL, "?#"); index >= 0 {
		lowerURL = lowerURL[:index]
	}
	switch {
	case strings.HasSuffix(lowerURL, ".svg"),
		strings.HasSuffix(lowerURL, ".png"),
		strings.HasSuffix(lowerURL, ".jpg"),
		strings.HasSuffix(lowerURL, ".jpeg"),
		strings.HasSuffix(lowerURL, ".webp"),
		strings.HasSuffix(lowerURL, ".gif"):
		return "image"
	case strings.HasSuffix(lowerURL, ".mp4"),
		strings.HasSuffix(lowerURL, ".mov"),
		strings.HasSuffix(lowerURL, ".webm"):
		return "video"
	case strings.HasSuffix(lowerURL, ".mp3"),
		strings.HasSuffix(lowerURL, ".wav"),
		strings.HasSuffix(lowerURL, ".m4a"),
		strings.HasSuffix(lowerURL, ".aac"),
		strings.HasSuffix(lowerURL, ".flac"):
		return "audio"
	case mediaURL != "":
		return "file"
	case item["text"] != nil || item["content"] != nil:
		return "text"
	default:
		return ""
	}
}

func easyAIOutputURLs(data []any) []string {
	output := make([]string, 0, len(data))
	for _, item := range data {
		if typed, ok := item.(map[string]any); ok {
			if mediaURL := easyAIOutputURL(typed); mediaURL != "" {
				output = append(output, mediaURL)
			}
		}
	}
	return output
}

func easyAIString(value any) string {
	typed, _ := value.(string)
	return strings.TrimSpace(typed)
}

func cloneEasyAIMap(source map[string]any) map[string]any {
	if len(source) == 0 {
		return map[string]any{}
	}
	result := make(map[string]any, len(source))
	for key, value := range source {
		result[key] = cloneEasyAIValue(value)
	}
	return result
}

func cloneEasyAIValue(value any) any {
	switch typed := value.(type) {
	case map[string]any:
		return cloneEasyAIMap(typed)
	case []any:
		next := make([]any, len(typed))
		for index, item := range typed {
			next[index] = cloneEasyAIValue(item)
		}
		return next
	case []string:
		return append([]string(nil), typed...)
	default:
		return value
	}
}
