package fixed

import (
	"math"
	"testing"
)

// The simulation's verifiability depends on this arithmetic behaving
// identically everywhere, including at the extremes. These tests attack the
// boundaries.

func TestMulByZeroAndOne(t *testing.T) {
	for _, v := range []int64{0, 1, -1, 1000, -1000, 1 << 20} {
		a := FromInt(v)
		if got := a.Mul(0); got != 0 {
			t.Errorf("%d * 0 = %v, want 0", v, got)
		}
		if got := a.Mul(One); got != a {
			t.Errorf("%d * 1 = %v, want %v", v, got, a)
		}
	}
}

func TestDivByOneAndSelf(t *testing.T) {
	for _, v := range []int64{1, -1, 7, -7, 1000, 1 << 20} {
		a := FromInt(v)
		if got := a.Div(One); got != a {
			t.Errorf("%d / 1 = %v, want %v", v, got, a)
		}
		if got := a.Div(a); got != One {
			t.Errorf("%d / %d = %v, want 1", v, v, got)
		}
	}
}

func TestDivByZeroPanics(t *testing.T) {
	defer func() {
		if recover() == nil {
			t.Fatal("division by zero did not panic")
		}
	}()
	_ = One.Div(0)
}

func TestSqrtOfNegativePanics(t *testing.T) {
	defer func() {
		if recover() == nil {
			t.Fatal("sqrt of a negative did not panic")
		}
	}()
	_ = Sqrt(FromInt(-1))
}

// Multiplication must stay associative-ish and exact for representable values,
// which is what keeps a replayed round identical to the original.
func TestMulIsExactForFractions(t *testing.T) {
	cases := []struct {
		a, b, want F
	}{
		{One / 2, One / 2, One / 4},
		{One / 4, One / 4, One / 16},
		{One / 2, One / 4, One / 8},
		{One * 3 / 2, One * 2, One * 3},
	}
	for _, c := range cases {
		if got := c.a.Mul(c.b); got != c.want {
			t.Errorf("%v * %v = %v, want %v", c.a, c.b, got, c.want)
		}
	}
}

// Round-tripping a value through multiply and divide must return it exactly
// for powers of two, where no precision can be lost.
func TestMulDivRoundTripOnPowersOfTwo(t *testing.T) {
	for shift := 0; shift < 20; shift++ {
		v := FromInt(1 << shift)
		for _, by := range []F{One * 2, One * 4, One * 8} {
			if got := v.Mul(by).Div(by); got != v {
				t.Errorf("2^%d round trip through %v gave %v, want %v", shift, by, got, v)
			}
		}
	}
}

func TestSqrtIsMonotonic(t *testing.T) {
	prev := Sqrt(0)
	for i := int64(1); i < 5000; i++ {
		cur := Sqrt(FromInt(i))
		if cur < prev {
			t.Fatalf("Sqrt decreased at %d: %v -> %v", i, prev, cur)
		}
		prev = cur
	}
}

// Sqrt must never overshoot: its square must not exceed the input.
func TestSqrtNeverOvershoots(t *testing.T) {
	for i := int64(0); i < 20000; i++ {
		a := FromInt(i)
		r := Sqrt(a)
		if r.Mul(r) > a {
			t.Fatalf("Sqrt(%d) = %v squares to %v, which exceeds %v", i, r, r.Mul(r), a)
		}
	}
}

func TestSqrtOfLargeValues(t *testing.T) {
	// Values in the range the crash curve actually produces, up to the
	// representable maximum.
	for _, v := range []int64{1_000_000, 12_960_000, 100_000_000, MaxInt} {
		a := FromInt(v)
		r := Sqrt(a)
		if r <= 0 {
			t.Fatalf("Sqrt(%d) = %v, want positive", v, r)
		}
		if r.Mul(r) > a {
			t.Fatalf("Sqrt(%d) overshoots", v)
		}
	}
}

func TestIntTruncatesTowardNegativeInfinity(t *testing.T) {
	cases := []struct {
		in   F
		want int64
	}{
		{One, 1},
		{One + One/2, 1},
		{One*2 - 1, 1},
		{0, 0},
		{-One, -1},
	}
	for _, c := range cases {
		if got := c.in.Int(); got != c.want {
			t.Errorf("(%v).Int() = %d, want %d", c.in, got, c.want)
		}
	}
}

func TestFromIntPanicsOutsideRange(t *testing.T) {
	for _, v := range []int64{MaxInt + 1, MinInt - 1, 4_000_000_000, -4_000_000_000} {
		func() {
			defer func() {
				if recover() == nil {
					t.Errorf("FromInt(%d) did not panic", v)
				}
			}()
			_ = FromInt(v)
		}()
	}
	// The boundaries themselves must be accepted.
	_ = FromInt(MaxInt)
	_ = FromInt(MinInt)
}

func TestStringNeverPanicsAcrossRange(t *testing.T) {
	values := []F{
		0, 1, -1, One, -One, One / 3, math.MaxInt64, math.MinInt64 + 1,
		FromInt(4_000_000),
	}
	for _, v := range values {
		if s := v.String(); s == "" {
			t.Errorf("String() of %d returned empty", int64(v))
		}
	}
}

// Addition and subtraction are plain integer ops, but the inverse property is
// what payout arithmetic relies on.
func TestAddSubAreInverse(t *testing.T) {
	for _, a := range []F{0, One, -One, One * 12345, One / 7} {
		for _, b := range []F{0, One, -One, One * 999} {
			if got := a.Add(b).Sub(b); got != a {
				t.Errorf("(%v + %v) - %v = %v, want %v", a, b, b, got, a)
			}
		}
	}
}

// Determinism check: the same operations in the same order must produce
// bit-identical results every time, which is the whole premise of replay.
func TestOperationsAreBitStable(t *testing.T) {
	compute := func() F {
		acc := One
		for i := int64(1); i < 500; i++ {
			acc = acc.Mul(One + One/F(i+1))
			acc = acc.Div(One + One/F(i+2))
			acc = acc.Add(FromInt(i % 3))
			acc = Sqrt(acc)
		}
		return acc
	}
	first := compute()
	for i := 0; i < 200; i++ {
		if got := compute(); got != first {
			t.Fatalf("run %d diverged: %v != %v", i, got, first)
		}
	}
}
