package httpx

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"testing"

	"bountyboard/internal/config"
	"bountyboard/internal/metrics"
	"bountyboard/internal/ulid"
)

func newTestServer(t *testing.T) *Server {
	t.Helper()
	t.Setenv("SESSION_SECRET", "0123456789abcdef0123456789abcdef")
	t.Setenv("CREDENTIALS_ENC_KEY", base64.StdEncoding.EncodeToString(make([]byte, 32)))
	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("config: %v", err)
	}
	log := slog.New(slog.NewTextHandler(io.Discard, nil))
	return New(cfg, log, metrics.NewRegistry(), nil, nil)
}

func get(t *testing.T, h http.Handler, path string) *httptest.ResponseRecorder {
	t.Helper()
	req := httptest.NewRequest(http.MethodGet, path, nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	return rec
}

func TestHealthz(t *testing.T) {
	s := newTestServer(t)
	rec := get(t, s.Handler(), "/healthz")
	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d", rec.Code)
	}
	var body map[string]string
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body["status"] != "ok" {
		t.Errorf("status field = %q", body["status"])
	}
	if id := rec.Header().Get("X-Request-Id"); !ulid.IsValid(id) {
		t.Errorf("X-Request-Id %q is not a ULID", id)
	}
}

func TestReadyzAggregation(t *testing.T) {
	tests := []struct {
		name       string
		checks     []ReadinessCheck
		wantStatus string
		wantCode   int
	}{
		{
			name:       "no checks",
			wantStatus: "ok",
			wantCode:   http.StatusOK,
		},
		{
			name: "all passing",
			checks: []ReadinessCheck{
				{Name: "mongo", Required: true, Probe: func(context.Context) error { return nil }},
				{Name: "atomizer", Probe: func(context.Context) error { return nil }},
			},
			wantStatus: "ok",
			wantCode:   http.StatusOK,
		},
		{
			name: "optional failing is non-fatal",
			checks: []ReadinessCheck{
				{Name: "mongo", Required: true, Probe: func(context.Context) error { return nil }},
				{Name: "atomizer", Probe: func(context.Context) error { return errors.New("down") }},
			},
			wantStatus: "degraded",
			wantCode:   http.StatusOK,
		},
		{
			name: "required failing gates readiness",
			checks: []ReadinessCheck{
				{Name: "mongo", Required: true, Probe: func(context.Context) error { return errors.New("no reachable servers") }},
			},
			wantStatus: "unavailable",
			wantCode:   http.StatusServiceUnavailable,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := newTestServer(t)
			for _, c := range tt.checks {
				s.AddReadinessCheck(c)
			}
			rec := get(t, s.Handler(), "/readyz")
			if rec.Code != tt.wantCode {
				t.Fatalf("code = %d, want %d", rec.Code, tt.wantCode)
			}
			var body struct {
				Status string                 `json:"status"`
				Checks map[string]checkResult `json:"checks"`
			}
			if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
				t.Fatal(err)
			}
			if body.Status != tt.wantStatus {
				t.Errorf("status = %q, want %q", body.Status, tt.wantStatus)
			}
			if len(body.Checks) != len(tt.checks) {
				t.Errorf("checks = %d, want %d", len(body.Checks), len(tt.checks))
			}
		})
	}
}

func TestMetricszCountsRequests(t *testing.T) {
	s := newTestServer(t)
	get(t, s.Handler(), "/healthz")
	get(t, s.Handler(), "/healthz")
	rec := get(t, s.Handler(), "/metricsz")
	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d", rec.Code)
	}
	var body struct {
		Counters map[string]int64 `json:"counters"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body.Counters["http_requests_total"] != 2 {
		t.Errorf("http_requests_total = %d, want 2", body.Counters["http_requests_total"])
	}
}

func TestPanicRecoveryEnvelope(t *testing.T) {
	s := newTestServer(t)
	mux := http.NewServeMux()
	mux.HandleFunc("GET /boom", func(http.ResponseWriter, *http.Request) { panic("kaboom") })
	h := s.withMiddleware(mux)

	rec := get(t, h, "/boom")
	if rec.Code != http.StatusInternalServerError {
		t.Fatalf("status = %d", rec.Code)
	}
	var env struct {
		Error struct {
			Code    string `json:"code"`
			Message string `json:"message"`
		} `json:"error"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &env); err != nil {
		t.Fatal(err)
	}
	if env.Error.Code != "internal" {
		t.Errorf("error code = %q", env.Error.Code)
	}
}

func TestUnknownRouteNotFound(t *testing.T) {
	s := newTestServer(t)
	rec := get(t, s.Handler(), "/nope")
	if rec.Code != http.StatusNotFound {
		t.Fatalf("status = %d, want 404", rec.Code)
	}
}
