package authrace

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"
)

func request(t *testing.T, client *http.Client, url, identity string, body Refund) (int, Result) {
	t.Helper()
	raw, err := json.Marshal(body)
	if err != nil {
		t.Fatal(err)
	}
	r, err := http.NewRequest(http.MethodPost, url+"/v1/refund", bytes.NewReader(raw))
	if err != nil {
		t.Fatal(err)
	}
	r.Header.Set("Authorization", "Bearer "+identity)
	response, err := client.Do(r)
	if err != nil {
		t.Fatal(err)
	}
	defer response.Body.Close()
	var result Result
	if err = json.NewDecoder(response.Body).Decode(&result); err != nil {
		t.Fatal(err)
	}
	return response.StatusCode, result
}
func TestCrossTenantExploitAndFix(t *testing.T) {
	payload := Refund{Tenant: "beta", Operation: "owned-by-beta", Amount: 10}
	for _, vulnerable := range []bool{true, false} {
		h := New(vulnerable)
		s := httptest.NewServer(h)
		status, result := request(t, s.Client(), s.URL, "alpha", payload)
		s.Close()
		if vulnerable {
			if status != http.StatusOK || !result.Granted || h.Balance("beta") != 10 {
				t.Fatal("fixture must demonstrate BOLA")
			}
		} else if status != http.StatusForbidden || h.Balance("beta") != 0 {
			t.Fatal("fix must deny cross-tenant write")
		}
	}
}
func TestConcurrentReplayMitigation(t *testing.T) {
	for _, vulnerable := range []bool{true, false} {
		h := New(vulnerable)
		s := httptest.NewServer(h)
		const workers = 32
		start := make(chan struct{})
		var wg sync.WaitGroup
		wg.Add(workers)
		results := make(chan Result, workers)
		for i := 0; i < workers; i++ {
			go func() {
				defer wg.Done()
				<-start
				_, result := request(t, s.Client(), s.URL, "alpha", Refund{Tenant: "alpha", Operation: "same-logical-refund", Amount: 7})
				results <- result
			}()
		}
		close(start)
		wg.Wait()
		close(results)
		applied := 0
		for result := range results {
			if result.Granted {
				applied++
			}
		}
		s.Close()
		if vulnerable {
			if applied != workers || h.Balance("alpha") != workers*7 {
				t.Fatalf("fixture didn't replicate replay: %d", applied)
			}
		} else if applied != 1 || h.Balance("alpha") != 7 {
			t.Fatalf("replay protection failed: %d applied; balance %d", applied, h.Balance("alpha"))
		}
	}
}
func TestFingerprintMismatchAndUnauthorized(t *testing.T) {
	h := New(false)
	s := httptest.NewServer(h)
	defer s.Close()
	base := Refund{Tenant: "alpha", Operation: "same-operation", Amount: 4}
	status, _ := request(t, s.Client(), s.URL, "alpha", base)
	if status != http.StatusOK {
		t.Fatal(status)
	}
	base.Amount = 6
	status, result := request(t, s.Client(), s.URL, "alpha", base)
	if status != http.StatusConflict || result.Reason != "operation_fingerprint_conflict" {
		t.Fatal(result)
	}
	status, _ = request(t, s.Client(), s.URL, "invalid", base)
	if status != http.StatusUnauthorized {
		t.Fatal("unknown identity accepted")
	}
	if h.Balance("alpha") != 4 {
		t.Fatalf("unexpected side effects: %d", h.Balance("alpha"))
	}
}
func TestInvalidPayloads(t *testing.T) {
	h := New(false)
	s := httptest.NewServer(h)
	defer s.Close()
	for _, x := range []Refund{
		{Tenant: "alpha", Operation: "short", Amount: 10},
		{Tenant: "alpha", Operation: "valid-operation", Amount: 0},
		{Tenant: "alpha", Operation: "valid-operation", Amount: 101},
		{Tenant: "gamma", Operation: "valid-operation", Amount: 10},
	} {
		status, result := request(t, s.Client(), s.URL, "alpha", x)
		if status != http.StatusBadRequest {
			t.Fatalf("invalid %v returned %d: %s", x, status, result.Reason)
		}
	}
	if h.Balance("alpha") != 0 {
		t.Fatal("bad requests changed balance")
	}
}
func BenchmarkAuthorizedMutation(b *testing.B) {
	h := New(false)
	for i := 0; i < b.N; i++ {
		raw := fmt.Sprintf(`{"tenant":"alpha","operation_id":"operation-%08d","amount":1}`, i)
		r := httptest.NewRequest(http.MethodPost, "/v1/refund", bytes.NewReader([]byte(raw)))
		r.Header.Set("Authorization", "Bearer alpha")
		h.ServeHTTP(httptest.NewRecorder(), r)
	}
}

func FuzzHardenedHTTPParser(f *testing.F) {
	f.Add([]byte(`{"tenant":"alpha","operation_id":"example-operation","amount":5}`))
	f.Add([]byte(`{"tenant":"beta","operation_id":"example-operation","amount":-1}`))
	f.Add([]byte("not-json"))
	f.Fuzz(func(t *testing.T, body []byte) {
		if len(body) > 4096 {
			return
		}
		h := New(false)
		r := httptest.NewRequest(http.MethodPost, "/v1/refund", bytes.NewReader(body))
		r.Header.Set("Authorization", "Bearer alpha")
		w := httptest.NewRecorder()
		h.ServeHTTP(w, r)
		if h.Balance("beta") != 0 {
			t.Fatal("cross-tenant side effect")
		}
		if h.Balance("alpha") < 0 {
			t.Fatal("negative balance")
		}
	})
}
