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/store"
)

func publicGatewayTask(task store.GatewayTask) store.GatewayTask {
	if task.Status == "failed" || task.Status == "cancelled" || task.ErrorCode != "" || task.ErrorMessage != "" {
		value := publicerror.FromFields(task.ErrorCode, firstNonEmpty(task.ErrorMessage, task.Error), taskErrorHTTPStatus(task), false)
		value = mergeStoredPublicError(task.PublicError, value)
		value = publicerror.WithIDs(value, task.RequestID, task.ID)
		publicerror.Observe(value)
		task.PublicError = &value
		task.ErrorCode = value.Code
		task.ErrorMessage = value.Message
		task.Error = value.Message
	}
	task.Attempts = append([]store.TaskAttempt(nil), task.Attempts...)
	for index := range task.Attempts {
		attempt := &task.Attempts[index]
		// User-facing task details expose the canonical task result. Attempt
		// snapshots can duplicate large input media or retain raw provider output,
		// so keep them available only on dedicated administrative surfaces.
		attempt.RequestSnapshot = nil
		attempt.ResponseSnapshot = nil
		if attempt.ErrorCode == "" && attempt.ErrorMessage == "" {
			continue
		}
		status := attempt.StatusCode
		if status <= 0 {
			status = http.StatusBadGateway
		}
		value := publicerror.FromFields(attempt.ErrorCode, attempt.ErrorMessage, status, attempt.Retryable)
		value = mergeStoredPublicError(attempt.PublicError, value)
		value = publicerror.WithIDs(value, attempt.RequestID, task.ID)
		attempt.PublicError = &value
		attempt.ErrorCode = value.Code
		attempt.ErrorMessage = value.Message
	}
	return task
}

func publicTaskError(task store.GatewayTask) publicerror.Error {
	status := taskErrorHTTPStatus(task)
	if status <= 0 {
		status = http.StatusBadGateway
	}
	value := publicerror.FromFields(task.ErrorCode, firstNonEmpty(task.ErrorMessage, task.Error, task.Message), status, false)
	value = mergeStoredPublicError(task.PublicError, value)
	return publicerror.WithIDs(value, task.RequestID, task.ID)
}

func mergeStoredPublicError(stored *publicerror.Error, derived publicerror.Error) publicerror.Error {
	if stored == nil || stored.Code == "" || stored.Source == "" {
		return derived
	}
	value := *stored
	if len(value.Details) == 0 && len(derived.Details) > 0 {
		value.Details = derived.Details
	}
	if message := safeDerivedUpstreamMessage(derived); message != "" {
		value.Message = message
	}
	return value
}

func safeDerivedUpstreamMessage(value publicerror.Error) string {
	upstream, _ := value.Details["upstreamError"].(map[string]any)
	message, _ := upstream["message"].(string)
	return strings.TrimSpace(message)
}

func taskErrorHTTPStatus(task store.GatewayTask) int {
	if task.PublicError != nil && task.PublicError.Source != "" && task.PublicError.HTTPStatus >= 400 {
		return task.PublicError.HTTPStatus
	}
	for index := len(task.Attempts) - 1; index >= 0; index-- {
		if status := task.Attempts[index].StatusCode; status >= 400 && status <= 599 {
			return status
		}
	}
	if status := publicErrorInt(task.Metrics["statusCode"]); status >= 400 && status <= 599 {
		return status
	}
	return storedTaskErrorStatus(task.ErrorCode)
}

func publicErrorInt(value any) int {
	switch typed := value.(type) {
	case int:
		return typed
	case int32:
		return int(typed)
	case int64:
		return int(typed)
	case float32:
		return int(typed)
	case float64:
		return int(typed)
	default:
		return 0
	}
}

func publicTaskList(items []store.GatewayTask) []store.GatewayTask {
	out := make([]store.GatewayTask, len(items))
	for index, task := range items {
		out[index] = publicGatewayTask(task)
	}
	return out
}

func publicErrorMap(value publicerror.Error) map[string]any {
	out := map[string]any{
		"code": value.Code, "message": value.Message, "category": value.Category, "source": value.Source,
		"httpStatus": value.HTTPStatus, "retryable": value.Retryable, "action": value.Action,
		"version": value.Version,
	}
	if value.RetryAfterSeconds > 0 {
		out["retryAfterSeconds"] = value.RetryAfterSeconds
	}
	if requestID := strings.TrimSpace(value.RequestID); requestID != "" {
		out["requestId"] = requestID
	}
	if taskID := strings.TrimSpace(value.TaskID); taskID != "" {
		out["taskId"] = taskID
	}
	if len(value.Details) > 0 {
		out["details"] = value.Details
	}
	return out
}

func publicErrorWithRetryAfter(value publicerror.Error, details map[string]any) publicerror.Error {
	if value.RetryAfterSeconds > 0 || details == nil {
		return value
	}
	switch typed := details["retryAfterSeconds"].(type) {
	case int:
		value.RetryAfterSeconds = typed
	case int32:
		value.RetryAfterSeconds = int(typed)
	case int64:
		value.RetryAfterSeconds = int(typed)
	case float64:
		value.RetryAfterSeconds = int(typed)
	}
	if value.RetryAfterSeconds < 0 {
		value.RetryAfterSeconds = 0
	}
	return value
}

func safePublicErrorDetails(details map[string]any, value publicerror.Error, includePublicError bool) map[string]any {
	out := map[string]any{}
	for _, key := range []string{"param", "retryAfterSeconds", "recoveryAt", "rateLimit", "pricing"} {
		if item, ok := details[key]; ok && item != nil {
			out[key] = item
		}
	}
	if value.Category == "request" {
		for _, key := range []string{"reason", "diagnosticId"} {
			if item, ok := details[key]; ok && item != nil {
				out[key] = item
			}
		}
	}
	for key, item := range value.Details {
		if item != nil {
			out[key] = item
		}
	}
	if includePublicError {
		out["publicError"] = value
	}
	return out
}
