package agent

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
)

const maxRuntimeErrorDetailDepth = 5

var runtimeErrorDetailKeys = []string{
	"additionalDetails",
	"additional_details",
	"message",
	"detail",
	"details",
	"reason",
	"description",
	"errors",
	"body",
	"text",
}

var runtimeErrorNestedDetailKeys = []string{
	"error",
	"cause",
	"data",
	"response",
	"payload",
	"codexErrorInfo",
	"codex_error_info",
}

func runtimeErrorMessageFromRaw(raw json.RawMessage) string {
	var fields struct {
		Name                   string `json:"name"`
		Type                   string `json:"type"`
		Message                string `json:"message"`
		AdditionalDetails      string `json:"additionalDetails"`
		AdditionalDetailsSnake string `json:"additional_details"`
	}
	if err := json.Unmarshal(raw, &fields); err != nil {
		return runtimeErrorDetailFromRaw(raw, nil, 0)
	}
	return runtimeErrorMessage(
		firstNonEmptyString(fields.Name, fields.Type),
		fields.Message,
		firstNonEmptyString(fields.AdditionalDetails, fields.AdditionalDetailsSnake),
		raw,
	)
}

func runtimeErrorMessage(errorName, errorMessage, additionalDetail string, raws ...json.RawMessage) string {
	errorName = strings.TrimSpace(errorName)
	errorMessage = strings.TrimSpace(errorMessage)
	additionalDetail = strings.TrimSpace(additionalDetail)

	primary := firstNonEmptyString(errorMessage, errorName)
	detail := additionalDetail
	if detail == "" {
		skip := runtimeErrorSkipValues(errorName, errorMessage, additionalDetail)
		for _, raw := range raws {
			detail = runtimeErrorDetailFromRaw(raw, skip, 0)
			if detail != "" {
				break
			}
		}
	}
	return combineRuntimeErrorMessage(primary, detail)
}

func combineRuntimeErrorMessage(primary, detail string) string {
	primary = strings.TrimSpace(primary)
	detail = strings.TrimSpace(detail)
	if primary == "" {
		return detail
	}
	if detail == "" || strings.EqualFold(primary, detail) {
		return primary
	}
	if strings.Contains(strings.ToLower(detail), strings.ToLower(primary)) {
		return detail
	}
	return primary + ": " + detail
}

func runtimeErrorSkipValues(values ...string) map[string]struct{} {
	skip := make(map[string]struct{})
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		skip[strings.ToLower(value)] = struct{}{}
	}
	return skip
}

func runtimeErrorDetailFromRaw(raw json.RawMessage, skip map[string]struct{}, depth int) string {
	if depth > maxRuntimeErrorDetailDepth {
		return ""
	}
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 || bytes.Equal(raw, []byte("null")) {
		return ""
	}

	switch raw[0] {
	case '"':
		var text string
		if err := json.Unmarshal(raw, &text); err != nil {
			return ""
		}
		return runtimeErrorCleanString(text, skip)
	case '{':
		var object map[string]json.RawMessage
		if err := json.Unmarshal(raw, &object); err != nil {
			return ""
		}
		for _, key := range runtimeErrorDetailKeys {
			value, ok := runtimeErrorObjectValue(object, key)
			if !ok {
				continue
			}
			if detail := runtimeErrorDetailFromRaw(value, skip, depth+1); detail != "" {
				return detail
			}
		}
		for _, key := range runtimeErrorNestedDetailKeys {
			value, ok := runtimeErrorObjectValue(object, key)
			if !ok {
				continue
			}
			if detail := runtimeErrorDetailFromRaw(value, skip, depth+1); detail != "" {
				return detail
			}
		}
		return runtimeErrorHTTPStatusFromObject(object, depth+1)
	case '[':
		var items []json.RawMessage
		if err := json.Unmarshal(raw, &items); err != nil {
			return ""
		}
		parts := make([]string, 0, len(items))
		for _, item := range items {
			detail := runtimeErrorDetailFromRaw(item, skip, depth+1)
			if detail == "" || runtimeErrorDetailAlreadyIncluded(parts, detail) {
				continue
			}
			parts = append(parts, detail)
			if len(parts) >= 3 {
				break
			}
		}
		return strings.Join(parts, "; ")
	default:
		return ""
	}
}

func runtimeErrorCleanString(text string, skip map[string]struct{}) string {
	text = strings.TrimSpace(text)
	if text == "" {
		return ""
	}
	if _, ok := skip[strings.ToLower(text)]; ok {
		return ""
	}
	return text
}

func runtimeErrorObjectValue(object map[string]json.RawMessage, key string) (json.RawMessage, bool) {
	if value, ok := object[key]; ok {
		return value, true
	}
	for candidate, value := range object {
		if strings.EqualFold(candidate, key) {
			return value, true
		}
	}
	return nil, false
}

func runtimeErrorDetailAlreadyIncluded(parts []string, detail string) bool {
	for _, part := range parts {
		if strings.EqualFold(part, detail) {
			return true
		}
	}
	return false
}

func runtimeErrorHTTPStatusFromObject(object map[string]json.RawMessage, depth int) string {
	if depth > maxRuntimeErrorDetailDepth {
		return ""
	}
	for _, key := range []string{"httpStatusCode", "http_status_code", "statusCode"} {
		if value, ok := runtimeErrorObjectValue(object, key); ok {
			if status := runtimeErrorHTTPStatus(value); status != "" {
				return status
			}
		}
	}
	for _, value := range object {
		if bytes.HasPrefix(bytes.TrimSpace(value), []byte("{")) {
			var nested map[string]json.RawMessage
			if err := json.Unmarshal(value, &nested); err == nil {
				if status := runtimeErrorHTTPStatusFromObject(nested, depth+1); status != "" {
					return status
				}
			}
		}
	}
	return ""
}

func runtimeErrorHTTPStatus(raw json.RawMessage) string {
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 || bytes.Equal(raw, []byte("null")) {
		return ""
	}
	var status int
	if err := json.Unmarshal(raw, &status); err == nil && status > 0 {
		return fmt.Sprintf("HTTP %d", status)
	}
	var text string
	if err := json.Unmarshal(raw, &text); err != nil {
		return ""
	}
	text = strings.TrimSpace(text)
	if text == "" {
		return ""
	}
	if strings.HasPrefix(strings.ToUpper(text), "HTTP ") {
		return text
	}
	return "HTTP " + text
}
