package quote

import (
	"context"
	"errors"
	"math"
	"net/http/httptest"
	"reflect"
	"testing"
)

type priceFake struct {
	unit int64
	err  error
	sku  string
}

func (f *priceFake) UnitPrice(ctx context.Context, sku string) (int64, error) {
	f.sku = sku
	if err := ctx.Err(); err != nil {
		return 0, err
	}
	return f.unit, f.err
}
func equalTotal(t *testing.T, got, want int64) {
	t.Helper()
	if got != want {
		t.Fatalf("total=%d want=%d", got, want)
	}
}
func TestTotal(t *testing.T) {
	cases := []struct {
		name string
		unit int64
		qty  int
		want int64
		bad  bool
	}{
		{"two_items", 250, 2, 500, false},
		{"free", 0, 1, 0, false},
		{"zero_quantity", 250, 0, 0, true},
		{"negative_price", -1, 1, 0, true},
		{"too_many", 1, 101, 0, true},
		{"overflow", math.MaxInt64, 2, 0, true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := Total(tc.unit, tc.qty)
			if (err != nil) != tc.bad {
				t.Fatalf("error=%v wantError=%v", err, tc.bad)
			}
			if !tc.bad {
				equalTotal(t, got, tc.want)
			}
		})
	}
}
func TestService(t *testing.T) {
	fake := &priceFake{unit: 250}
	svc := NewService(fake)
	got, err := svc.Quote(t.Context(), "pen", 2)
	if err != nil {
		t.Fatal(err)
	}
	equalTotal(t, got, 500)
	if fake.sku != "pen" {
		t.Fatalf("lookup sku=%q", fake.sku)
	}
	fake.err = errors.New("offline")
	if _, err = svc.Quote(t.Context(), "pen", 2); !errors.Is(err, fake.err) {
		t.Fatal(err)
	}
}
func TestHandler(t *testing.T) {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/quote?sku=pen&qty=2", nil)
	Handler(NewService(&priceFake{unit: 250})).ServeHTTP(rec, req)
	if rec.Code != 200 || rec.Body.String() != "total=500\n" {
		t.Fatalf("status=%d body=%q", rec.Code, rec.Body.String())
	}
	t.Logf("status=%d body=%q", rec.Code, rec.Body.String())
}
func TestCleanupOrder(t *testing.T) {
	var got []string
	t.Run("scope", func(t *testing.T) {
		t.Cleanup(func() { got = append(got, "first") })
		t.Cleanup(func() { got = append(got, "second") })
	})
	if !reflect.DeepEqual(got, []string{"second", "first"}) {
		t.Fatal(got)
	}
	t.Logf("cleanup=%v", got)
}
func FuzzTotal(f *testing.F) {
	f.Add(int64(250), 2)
	f.Add(int64(0), 1)
	f.Fuzz(func(t *testing.T, unit int64, qty int) {
		total, err := Total(unit, qty)
		valid := qty > 0 && qty <= 100 && unit >= 0 && unit <= math.MaxInt64/int64(qty)
		if valid != (err == nil) {
			t.Fatalf("valid=%v error=%v", valid, err)
		}
		if valid && total/int64(qty) != unit {
			t.Fatalf("roundtrip: %d * %d = %d", unit, qty, total)
		}
	})
}

var sink int64

func BenchmarkTotal(b *testing.B) {
	for b.Loop() {
		got, err := Total(250, 2)
		if err != nil {
			b.Fatal(err)
		}
		sink = got
	}
}
