package atomize

import (
	"context"
	"encoding/json"
	"errors"
	"math"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"bountyboard/internal/extsvc"
)

func TestNormalizeCoefficients(t *testing.T) {
	tests := []struct {
		name    string
		in      []float64
		wantErr bool
		wantSum float64
	}{
		{"exact sum kept", []float64{0.2, 0.5, 0.3}, false, 1},
		{"tiny deviation accepted", []float64{0.2, 0.5, 0.3001}, false, 1},
		{"small deviation renormalized", []float64{0.21, 0.52, 0.31}, false, 1}, // sum 1.04
		{"under-sum renormalized", []float64{0.3, 0.3, 0.36}, false, 1},         // sum 0.96
		{"large deviation rejected", []float64{0.5, 0.5, 0.5}, true, 0},
		{"zero coefficient rejected", []float64{0, 0.5, 0.5}, true, 0},
		{"negative rejected", []float64{-0.1, 0.6, 0.5}, true, 0},
		{"above one rejected", []float64{1.2}, true, 0},
		{"empty rejected", nil, true, 0},
		{"single exact", []float64{1.0}, false, 1},
		{"NaN rejected", []float64{math.NaN(), 0.5}, true, 0},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			out, err := NormalizeCoefficients(tt.in)
			if tt.wantErr {
				if !errors.Is(err, ErrBadCoefficients) {
					t.Fatalf("want ErrBadCoefficients, got %v", err)
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			sum := 0.0
			for _, c := range out {
				if c <= 0 || c > 1 {
					t.Errorf("normalized coefficient %v out of range", c)
				}
				sum += c
			}
			if math.Abs(sum-tt.wantSum) > 1e-9 {
				t.Errorf("sum = %.6f, want exactly %v (coeffs %v)", sum, tt.wantSum, out)
			}
		})
	}
}

func fakeAtomizer(t *testing.T, handler http.HandlerFunc) *Client {
	t.Helper()
	srv := httptest.NewServer(handler)
	t.Cleanup(srv.Close)
	return New(func() string { return srv.URL }, "test-token", 5*time.Second)
}

func TestAtomizeAppliesNormalization(t *testing.T) {
	client := fakeAtomizer(t, func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") != "Bearer test-token" {
			t.Errorf("missing bearer token")
		}
		var req AtomizeRequest
		json.NewDecoder(r.Body).Decode(&req)
		if req.TaskID != "01JTASK" {
			t.Errorf("taskId = %q", req.TaskID)
		}
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(AtomizeResponse{Tasks: []SubTask{
			{Title: "A", EffortCoefficient: 0.31}, // sum 1.03 → renormalize
			{Title: "B", EffortCoefficient: 0.41},
			{Title: "C", EffortCoefficient: 0.31},
		}})
	})
	resp, err := client.Atomize(context.Background(), AtomizeRequest{TaskID: "01JTASK"})
	if err != nil {
		t.Fatal(err)
	}
	sum := 0.0
	for _, st := range resp.Tasks {
		sum += st.EffortCoefficient
	}
	if math.Abs(sum-1) > 1e-9 {
		t.Fatalf("normalized sum = %v", sum)
	}
}

func TestAtomizeRejectsBadSum(t *testing.T) {
	client := fakeAtomizer(t, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(AtomizeResponse{Tasks: []SubTask{
			{Title: "A", EffortCoefficient: 0.9},
			{Title: "B", EffortCoefficient: 0.9},
		}})
	})
	_, err := client.Atomize(context.Background(), AtomizeRequest{})
	if !errors.Is(err, ErrBadCoefficients) {
		t.Fatalf("want ErrBadCoefficients, got %v", err)
	}
}

func TestExtendValidatesCoefficient(t *testing.T) {
	coeff := 2.5
	client := fakeAtomizer(t, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(ExtendResponse{Task: SubTask{Title: "X", EffortCoefficient: coeff}})
	})
	if _, err := client.Extend(context.Background(), ExtendRequest{}); !errors.Is(err, ErrBadCoefficients) {
		t.Fatalf("coefficient 2.5: want ErrBadCoefficients, got %v", err)
	}
	coeff = 1.4 // legal for extensions
	if _, err := client.Extend(context.Background(), ExtendRequest{}); err != nil {
		t.Fatalf("coefficient 1.4 should pass: %v", err)
	}
}

func TestBreakerOpensAfterRepeatedFailures(t *testing.T) {
	client := fakeAtomizer(t, func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "boom", http.StatusInternalServerError)
	})
	for i := 0; i < 3; i++ {
		if _, err := client.Atomize(context.Background(), AtomizeRequest{}); err == nil {
			t.Fatal("expected failure")
		}
	}
	if client.BreakerState() != "open" {
		t.Fatalf("breaker = %s, want open", client.BreakerState())
	}
	_, err := client.Atomize(context.Background(), AtomizeRequest{})
	if !errors.Is(err, extsvc.ErrBreakerOpen) {
		t.Fatalf("want ErrBreakerOpen, got %v", err)
	}
}
