package agent

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/drivercontract"
)

const (
	transientRuntimeErrorMetadataKey       = "transient_runtime_error"
	transientRuntimeErrorReasonMetadataKey = "transient_runtime_error_reason"
	transientRuntimeErrorStatusMetadataKey = "http_status"

	transientRuntimeRetryMaxAttempts = 3
	transientRuntimeRetryTurnEndWait = 500 * time.Millisecond
)

var runtimeErrorHTTPStatusPattern = regexp.MustCompile(`(?i)\b(?:http\s*)?([1-5][0-9]{2})\b`)

type transientRuntimeErrorClassification struct {
	Retryable  bool
	Reason     string
	HTTPStatus int
}

func transientRuntimeErrorMetadata(reason string, status int) map[string]any {
	metadata := map[string]any{
		transientRuntimeErrorMetadataKey: true,
	}
	if trimmedReason := strings.TrimSpace(reason); trimmedReason != "" {
		metadata[transientRuntimeErrorReasonMetadataKey] = trimmedReason
	}
	if status > 0 {
		metadata[transientRuntimeErrorStatusMetadataKey] = status
	}
	return metadata
}

func mergeTransientRuntimeErrorMetadata(metadata map[string]any, classification transientRuntimeErrorClassification) map[string]any {
	if !classification.Retryable {
		return metadata
	}
	merged := make(map[string]any, len(metadata)+3)
	for key, value := range metadata {
		merged[key] = value
	}
	for key, value := range transientRuntimeErrorMetadata(classification.Reason, classification.HTTPStatus) {
		merged[key] = value
	}
	return merged
}

func classifyTransientRuntimeError(ev drivercontract.ParsedEvent) transientRuntimeErrorClassification {
	if len(ev.Metadata) > 0 && parsedBoolMetadata(ev.Metadata, transientRuntimeErrorMetadataKey) {
		status := parsedIntMetadata(ev.Metadata, transientRuntimeErrorStatusMetadataKey)
		reason := strings.TrimSpace(parsedStringMetadata(ev.Metadata, transientRuntimeErrorReasonMetadataKey))
		if reason == "" {
			reason = transientRuntimeErrorReasonForStatus(status)
		}
		if reason == "" {
			reason = "transient_provider_error"
		}
		return transientRuntimeErrorClassification{Retryable: true, Reason: reason, HTTPStatus: status}
	}

	text := strings.TrimSpace(firstNonEmptyString(ev.Message, ev.Text))
	return classifyTransientRuntimeErrorText(text)
}

func classifyTransientRuntimeErrorText(text string) transientRuntimeErrorClassification {
	text = strings.TrimSpace(text)
	if text == "" {
		return transientRuntimeErrorClassification{}
	}
	lower := strings.ToLower(text)
	status := transientRuntimeErrorHTTPStatus(text)
	if transientRuntimeErrorStatusRetryable(status) {
		return transientRuntimeErrorClassification{
			Retryable:  true,
			Reason:     transientRuntimeErrorReasonForStatus(status),
			HTTPStatus: status,
		}
	}
	switch {
	case strings.Contains(lower, "overloaded"),
		strings.Contains(lower, "overload"),
		strings.Contains(lower, "访问量过大"),
		strings.Contains(lower, "稍后再试"):
		return transientRuntimeErrorClassification{Retryable: true, Reason: "overloaded"}
	case strings.Contains(lower, "rate limit"),
		strings.Contains(lower, "rate_limit"),
		strings.Contains(lower, "too many requests"):
		return transientRuntimeErrorClassification{Retryable: true, Reason: "rate_limited"}
	default:
		return transientRuntimeErrorClassification{}
	}
}

func classifyTransientRuntimeErrorRaw(raw json.RawMessage) transientRuntimeErrorClassification {
	detail := runtimeErrorDetailFromRaw(raw, nil, 0)
	if classification := classifyTransientRuntimeErrorText(detail); classification.Retryable {
		return classification
	}
	return classifyTransientRuntimeErrorText(string(raw))
}

func transientRuntimeErrorHTTPStatus(text string) int {
	for _, match := range runtimeErrorHTTPStatusPattern.FindAllStringSubmatch(text, -1) {
		if len(match) < 2 {
			continue
		}
		status, err := strconv.Atoi(match[1])
		if err != nil {
			continue
		}
		if status >= 100 && status <= 599 {
			return status
		}
	}
	return 0
}

func transientRuntimeErrorStatusRetryable(status int) bool {
	switch status {
	case 429, 502, 503, 504, 529:
		return true
	default:
		return false
	}
}

func transientRuntimeErrorReasonForStatus(status int) string {
	switch status {
	case 429:
		return "rate_limited"
	case 529:
		return "overloaded"
	case 502, 503, 504:
		return "upstream_5xx"
	default:
		return ""
	}
}

func parsedStringMetadata(metadata map[string]any, key string) string {
	if len(metadata) == 0 {
		return ""
	}
	value, ok := metadata[key]
	if !ok {
		return ""
	}
	switch v := value.(type) {
	case string:
		return v
	case fmt.Stringer:
		return v.String()
	default:
		return fmt.Sprint(v)
	}
}

func parsedIntMetadata(metadata map[string]any, key string) int {
	if len(metadata) == 0 {
		return 0
	}
	value, ok := metadata[key]
	if !ok {
		return 0
	}
	switch v := value.(type) {
	case int:
		return v
	case int64:
		return int(v)
	case float64:
		return int(v)
	case json.Number:
		n, _ := v.Int64()
		return int(n)
	case string:
		n, _ := strconv.Atoi(strings.TrimSpace(v))
		return n
	default:
		return 0
	}
}

func transientRuntimeRetryDelay(attempt int) time.Duration {
	switch {
	case attempt <= 1:
		return jitterTransientRuntimeRetryDelay(2 * time.Second)
	case attempt == 2:
		return jitterTransientRuntimeRetryDelay(5 * time.Second)
	default:
		return jitterTransientRuntimeRetryDelay(15 * time.Second)
	}
}

var transientRuntimeRetryDelayFunc = transientRuntimeRetryDelay

func setTransientRuntimeRetryDelayFuncForTest(fn func(int) time.Duration) func() {
	previous := transientRuntimeRetryDelayFunc
	transientRuntimeRetryDelayFunc = fn
	return func() {
		transientRuntimeRetryDelayFunc = previous
	}
}

func jitterTransientRuntimeRetryDelay(base time.Duration) time.Duration {
	if base <= 0 {
		return 0
	}
	jitter := time.Duration(rand.Int63n(int64(base / 3)))
	return base + jitter
}
