package service

import (
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/dto"
)

const (
	metricsMaxSamplesCode       = "MAX_SAMPLES_PER_QUERY"
	metricsUnprocessableCode    = "METRICS_UPSTREAM_UNPROCESSABLE"
	metricsUnprocessableMessage = "metrics upstream rejected the query as unprocessable"
)

var maxSamplesPerQueryPattern = regexp.MustCompile(`(?i)cannot select more than -search\.maxSamplesPerQuery=([1-9][0-9]*) samples`)

var (
	metricsURLPattern        = regexp.MustCompile(`(?i)\b(?:https?|ftp)://[^\s]+`)
	metricsIPv4Pattern       = regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?::[0-9]{1,5})?\b`)
	metricsHostnamePattern   = regexp.MustCompile(`\b[a-zA-Z0-9][a-zA-Z0-9-]*(?:\.[a-zA-Z0-9][a-zA-Z0-9-]*){2,}(?::[0-9]{1,5})?\b`)
	metricsPathPattern       = regexp.MustCompile(`(?:^|\s)/(?:[^\s;,.]+/)*[^\s;,.]*`)
	metricsIdentifierPattern = regexp.MustCompile(`\b[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z0-9_]+\b`)
)

type prometheusUpstreamError struct {
	Status    string `json:"status"`
	ErrorType string `json:"errorType"`
	Error     string `json:"error"`
}

type metricsUpstreamDiagnostics struct {
	Code              string
	UpstreamErrorType string
	Sanitized         bool
}

func newMetricsUpstreamErrorResponse(statusCode int, body []byte) (*dto.PrometheusResponse, metricsUpstreamDiagnostics) {
	if statusCode != http.StatusUnprocessableEntity {
		return &dto.PrometheusResponse{
			Status:         "error",
			ErrorType:      "execution",
			Error:          fmt.Sprintf("metrics upstream returned HTTP status %d", statusCode),
			UpstreamStatus: statusCode,
		}, metricsUpstreamDiagnostics{Sanitized: true}
	}

	upstream := prometheusUpstreamError{}
	structured := json.Unmarshal(body, &upstream) == nil &&
		strings.EqualFold(strings.TrimSpace(upstream.Status), "error") &&
		strings.TrimSpace(upstream.Error) != ""

	code := metricsUnprocessableCode
	message := metricsUnprocessableMessage
	upstreamType := ""
	if structured {
		upstreamType = safeUpstreamErrorType(upstream.ErrorType)
		if matches := maxSamplesPerQueryPattern.FindStringSubmatch(upstream.Error); len(matches) == 2 {
			if limit, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
				code = metricsMaxSamplesCode
				message = fmt.Sprintf(
					"cannot select more than -search.maxSamplesPerQuery=%d samples; reduce the query range or add more specific label filters",
					limit,
				)
			}
		} else if sanitizedReason, ok := sanitizeMetricsUpstreamReason(upstream.Error); ok {
			message = sanitizedReason
		}
	}

	return &dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "execution",
			Error:     message,
			Code:      code,
			Details: &dto.MetricsErrorDetails{
				UpstreamStatus:    statusCode,
				UpstreamErrorType: upstreamType,
				Retryable:         false,
			},
			UpstreamStatus: statusCode,
		}, metricsUpstreamDiagnostics{
			Code:              code,
			UpstreamErrorType: upstreamType,
			Sanitized:         true,
		}
}

func sanitizeMetricsUpstreamReason(value string) (string, bool) {
	value = strings.TrimSpace(value)
	if value == "" || len(value) > 1024 || strings.ContainsAny(value, "{}[]()`\"") {
		return "", false
	}
	for _, r := range value {
		if r < 0x20 || r == 0x7f {
			return "", false
		}
	}
	value = metricsURLPattern.ReplaceAllString(value, "[redacted-url]")
	value = metricsIPv4Pattern.ReplaceAllString(value, "[redacted-address]")
	value = metricsHostnamePattern.ReplaceAllString(value, "[redacted-host]")
	value = metricsIdentifierPattern.ReplaceAllString(value, "[redacted-identifier]")
	value = metricsPathPattern.ReplaceAllStringFunc(value, func(match string) string {
		if strings.HasPrefix(match, " ") {
			return " [redacted-path]"
		}
		return "[redacted-path]"
	})
	value = strings.TrimSpace(value)
	if value == "" {
		return "", false
	}
	return value, true
}

func safeUpstreamErrorType(value string) string {
	value = strings.TrimSpace(value)
	if value == "" || len(value) > 64 {
		return ""
	}
	for _, r := range value {
		if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && (r < '0' || r > '9') && r != '_' && r != '-' {
			return ""
		}
	}
	return value
}
