package p256k1

import (
	"testing"
)

func TestAVX2Integration(t *testing.T) {
	t.Logf("AVX2 CPU support: %v", HasAVX2CPU())
	t.Logf("AVX2 enabled: %v", HasAVX2())

	// Test scalar multiplication with AVX2
	var a, b, productAVX, productGo Scalar
	a.setInt(12345)
	b.setInt(67890)

	// Compute with AVX2 enabled
	SetAVX2Enabled(true)
	productAVX.mul(&a, &b)

	// Compute with AVX2 disabled
	SetAVX2Enabled(false)
	productGo.mulPureGo(&a, &b)

	// Re-enable AVX2
	SetAVX2Enabled(true)

	if !productAVX.equal(&productGo) {
		t.Errorf("AVX2 and Go scalar multiplication differ:\n  AVX2: %v\n  Go: %v",
			productAVX.d, productGo.d)
	} else {
		t.Logf("Scalar multiplication matches: %v", productAVX.d)
	}

	// Test scalar addition
	var sumAVX, sumGo Scalar
	SetAVX2Enabled(true)
	sumAVX.add(&a, &b)

	SetAVX2Enabled(false)
	sumGo.addPureGo(&a, &b)

	SetAVX2Enabled(true)

	if !sumAVX.equal(&sumGo) {
		t.Errorf("AVX2 and Go scalar addition differ:\n  AVX2: %v\n  Go: %v",
			sumAVX.d, sumGo.d)
	} else {
		t.Logf("Scalar addition matches: %v", sumAVX.d)
	}

	// Test inverse (which uses mul internally)
	var inv, product Scalar
	a.setInt(2)

	SetAVX2Enabled(true)
	inv.inverse(&a)
	product.mul(&a, &inv)

	t.Logf("a = %v", a.d)
	t.Logf("inv(a) = %v", inv.d)
	t.Logf("a * inv(a) = %v", product.d)
	t.Logf("isOne = %v", product.isOne())

	if !product.isOne() {
		// Try with pure Go
		SetAVX2Enabled(false)
		var inv2, product2 Scalar
		inv2.inverse(&a)
		product2.mul(&a, &inv2)
		t.Logf("Pure Go: a * inv(a) = %v", product2.d)
		t.Logf("Pure Go isOne = %v", product2.isOne())
		SetAVX2Enabled(true)

		t.Errorf("2 * inv(2) should equal 1")
	}
}

func TestScalarMulAVX2VsPureGo(t *testing.T) {
	if !HasAVX2CPU() {
		t.Skip("AVX2 not available")
	}

	// Test several multiplication cases
	testCases := []struct {
		a, b uint
	}{
		{2, 3},
		{12345, 67890},
		{0xFFFFFFFF, 0xFFFFFFFF},
		{1, 1},
		{0, 123},
	}

	for _, tc := range testCases {
		var a, b, productAVX, productGo Scalar
		a.setInt(tc.a)
		b.setInt(tc.b)

		SetAVX2Enabled(true)
		scalarMulAVX2(&productAVX, &a, &b)

		productGo.mulPureGo(&a, &b)

		if !productAVX.equal(&productGo) {
			t.Errorf("Mismatch for %d * %d:\n  AVX2: %v\n  Go: %v",
				tc.a, tc.b, productAVX.d, productGo.d)
		}
	}
}

func TestScalarMulAVX2Large(t *testing.T) {
	if !HasAVX2CPU() {
		t.Skip("AVX2 not available")
	}

	// Test with the actual inverse of 2
	var a Scalar
	a.setInt(2)

	var inv Scalar
	SetAVX2Enabled(false)
	inv.inverse(&a)
	SetAVX2Enabled(true)

	t.Logf("a = %v", a.d)
	t.Logf("inv(2) = %v", inv.d)

	// Test multiplication of 2 * inv(2)
	var productAVX, productGo Scalar
	scalarMulAVX2(&productAVX, &a, &inv)

	SetAVX2Enabled(false)
	productGo.mulPureGo(&a, &inv)
	SetAVX2Enabled(true)

	t.Logf("AVX2: 2 * inv(2) = %v", productAVX.d)
	t.Logf("Go:   2 * inv(2) = %v", productGo.d)

	if !productAVX.equal(&productGo) {
		t.Errorf("Large number multiplication differs")
	}
}

func TestInverseAVX2VsGo(t *testing.T) {
	if !HasAVX2CPU() {
		t.Skip("AVX2 not available")
	}

	var a Scalar
	a.setInt(2)

	// Compute inverse with AVX2
	var invAVX Scalar
	SetAVX2Enabled(true)
	invAVX.inverse(&a)

	// Compute inverse with pure Go
	var invGo Scalar
	SetAVX2Enabled(false)
	invGo.inverse(&a)
	SetAVX2Enabled(true)

	t.Logf("AVX2 inv(2) = %v", invAVX.d)
	t.Logf("Go   inv(2) = %v", invGo.d)

	if !invAVX.equal(&invGo) {
		t.Errorf("Inverse differs between AVX2 and Go")
	}
}

func TestScalarMulAliased(t *testing.T) {
	if !HasAVX2CPU() {
		t.Skip("AVX2 not available")
	}

	// Test aliased multiplication: r.mul(r, &b) and r.mul(&a, r)
	var a, b Scalar
	a.setInt(12345)
	b.setInt(67890)

	// Test r = r * b
	var rAVX, rGo Scalar
	rAVX = a
	rGo = a

	SetAVX2Enabled(true)
	scalarMulAVX2(&rAVX, &rAVX, &b)

	SetAVX2Enabled(false)
	rGo.mulPureGo(&rGo, &b)
	SetAVX2Enabled(true)

	if !rAVX.equal(&rGo) {
		t.Errorf("r = r * b failed:\n  AVX2: %v\n  Go: %v", rAVX.d, rGo.d)
	}

	// Test r = a * r
	rAVX = b
	rGo = b

	SetAVX2Enabled(true)
	scalarMulAVX2(&rAVX, &a, &rAVX)

	SetAVX2Enabled(false)
	rGo.mulPureGo(&a, &rGo)
	SetAVX2Enabled(true)

	if !rAVX.equal(&rGo) {
		t.Errorf("r = a * r failed:\n  AVX2: %v\n  Go: %v", rAVX.d, rGo.d)
	}

	// Test squaring: r = r * r
	rAVX = a
	rGo = a

	SetAVX2Enabled(true)
	scalarMulAVX2(&rAVX, &rAVX, &rAVX)

	SetAVX2Enabled(false)
	rGo.mulPureGo(&rGo, &rGo)
	SetAVX2Enabled(true)

	if !rAVX.equal(&rGo) {
		t.Errorf("r = r * r failed:\n  AVX2: %v\n  Go: %v", rAVX.d, rGo.d)
	}
}

func TestScalarMulLargeNumbers(t *testing.T) {
	if !HasAVX2CPU() {
		t.Skip("AVX2 not available")
	}

	// Test with large numbers (all limbs non-zero)
	testCases := []struct {
		name string
		a, b Scalar
	}{
		{
			name: "large a * small b",
			a:    Scalar{d: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0, 0}},
			b:    Scalar{d: [4]uint64{2, 0, 0, 0}},
		},
		{
			name: "a^2 where a is large",
			a:    Scalar{d: [4]uint64{0x123456789ABCDEF0, 0xFEDCBA9876543210, 0, 0}},
			b:    Scalar{d: [4]uint64{0x123456789ABCDEF0, 0xFEDCBA9876543210, 0, 0}},
		},
		{
			name: "full limbs",
			a:    Scalar{d: [4]uint64{0x123456789ABCDEF0, 0xFEDCBA9876543210, 0x1111111111111111, 0x2222222222222222}},
			b:    Scalar{d: [4]uint64{0x0FEDCBA987654321, 0x123456789ABCDEF0, 0x3333333333333333, 0x4444444444444444}},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var productAVX, productGo Scalar

			SetAVX2Enabled(true)
			scalarMulAVX2(&productAVX, &tc.a, &tc.b)

			SetAVX2Enabled(false)
			productGo.mulPureGo(&tc.a, &tc.b)
			SetAVX2Enabled(true)

			if !productAVX.equal(&productGo) {
				t.Errorf("Mismatch:\n  a: %v\n  b: %v\n  AVX2: %v\n  Go: %v",
					tc.a.d, tc.b.d, productAVX.d, productGo.d)
			}
		})
	}
}
