package gateway

import (
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

var latencyBuckets = [...]time.Duration{
	5 * time.Millisecond,
	10 * time.Millisecond,
	25 * time.Millisecond,
	50 * time.Millisecond,
	100 * time.Millisecond,
	250 * time.Millisecond,
	500 * time.Millisecond,
	time.Second,
	2 * time.Second,
	5 * time.Second,
	10 * time.Second,
}

type metrics struct {
	accepted       atomic.Uint64
	rejectedDrain  atomic.Uint64
	rejectedFull   atomic.Uint64
	rejectedSize   atomic.Uint64
	rejectedBody   atomic.Uint64
	timeouts       atomic.Uint64
	upstreamErrors atomic.Uint64
	inFlight       atomic.Int64
	latencyCount   atomic.Uint64
	latencyNanos   atomic.Uint64
	latencyBuckets [len(latencyBuckets)]atomic.Uint64
}

func (m *metrics) observeLatency(duration time.Duration) {
	m.latencyCount.Add(1)
	m.latencyNanos.Add(uint64(duration))
	for i, boundary := range latencyBuckets {
		if duration <= boundary {
			m.latencyBuckets[i].Add(1)
		}
	}
}

func (m *metrics) writePrometheus(w io.Writer, routeConfig RouteConfig) {
	metric(w, "nexuscrm_gateway_requests_accepted_total", "Accepted routed requests.", m.accepted.Load())
	metric(w, "nexuscrm_gateway_requests_rejected_draining_total", "Requests rejected while the process is draining.", m.rejectedDrain.Load())
	metric(w, "nexuscrm_gateway_requests_rejected_admission_total", "Requests rejected by fail-fast concurrency admission.", m.rejectedFull.Load())
	metric(w, "nexuscrm_gateway_requests_rejected_size_total", "Requests rejected by the configured body-size envelope.", m.rejectedSize.Load())
	metric(w, "nexuscrm_gateway_requests_rejected_body_total", "Requests rejected because their body could not be read.", m.rejectedBody.Load())
	metric(w, "nexuscrm_gateway_request_timeouts_total", "Requests terminated by the configured route deadline.", m.timeouts.Load())
	metric(w, "nexuscrm_gateway_upstream_errors_total", "Upstream transport failures.", m.upstreamErrors.Load())
	gauge(w, "nexuscrm_gateway_requests_in_flight", "Routed handlers currently in flight.", m.inFlight.Load())
	_, _ = fmt.Fprintf(w, "# HELP nexuscrm_gateway_request_duration_seconds Routed request latency.\n# TYPE nexuscrm_gateway_request_duration_seconds histogram\n")
	for i, boundary := range latencyBuckets {
		_, _ = fmt.Fprintf(w, "nexuscrm_gateway_request_duration_seconds_bucket{le=%q} %d\n", strconv.FormatFloat(boundary.Seconds(), 'f', -1, 64), m.latencyBuckets[i].Load())
	}
	count := m.latencyCount.Load()
	_, _ = fmt.Fprintf(w, "nexuscrm_gateway_request_duration_seconds_bucket{le=\"+Inf\"} %d\n", count)
	_, _ = fmt.Fprintf(w, "nexuscrm_gateway_request_duration_seconds_sum %s\n", strconv.FormatFloat(float64(m.latencyNanos.Load())/float64(time.Second), 'f', 9, 64))
	_, _ = fmt.Fprintf(w, "nexuscrm_gateway_request_duration_seconds_count %d\n", count)
	_, _ = fmt.Fprintf(w, "# HELP nexuscrm_gateway_route_config_info Loaded immutable route configuration.\n# TYPE nexuscrm_gateway_route_config_info gauge\n")
	_, _ = fmt.Fprintf(w, "nexuscrm_gateway_route_config_info{api_version=%q,revision=%q,digest=%q} 1\n", prometheusLabel(routeConfig.APIVersion), prometheusLabel(routeConfig.Revision), prometheusLabel(routeConfig.Digest))
}

func metric(w io.Writer, name, help string, value uint64) {
	_, _ = fmt.Fprintf(w, "# HELP %s %s\n# TYPE %s counter\n%s %d\n", name, help, name, name, value)
}

func gauge(w io.Writer, name, help string, value int64) {
	_, _ = fmt.Fprintf(w, "# HELP %s %s\n# TYPE %s gauge\n%s %d\n", name, help, name, name, value)
}

func prometheusLabel(value string) string {
	replacer := strings.NewReplacer("\\", "\\\\", "\n", "\\n", "\"", "\\\"")
	return replacer.Replace(value)
}
