package p256k1

import (
	"testing"
)

// fieldMulPureGo is the pure Go implementation for comparison
func fieldMulPureGo(r, a, b *FieldElement) {
	// Extract limbs for easier access
	a0, a1, a2, a3, a4 := a.n[0], a.n[1], a.n[2], a.n[3], a.n[4]
	b0, b1, b2, b3, b4 := b.n[0], b.n[1], b.n[2], b.n[3], b.n[4]

	const M = uint64(0xFFFFFFFFFFFFF)            // 2^52 - 1
	const R = uint64(fieldReductionConstantShifted) // 0x1000003D10

	// Following the C implementation algorithm exactly
	var c, d uint128
	d = mulU64ToU128(a0, b3)
	d = addMulU128(d, a1, b2)
	d = addMulU128(d, a2, b1)
	d = addMulU128(d, a3, b0)

	c = mulU64ToU128(a4, b4)

	d = addMulU128(d, R, c.lo())
	c = c.rshift(64)

	t3 := d.lo() & M
	d = d.rshift(52)

	d = addMulU128(d, a0, b4)
	d = addMulU128(d, a1, b3)
	d = addMulU128(d, a2, b2)
	d = addMulU128(d, a3, b1)
	d = addMulU128(d, a4, b0)

	d = addMulU128(d, R<<12, c.lo())

	t4 := d.lo() & M
	d = d.rshift(52)
	tx := t4 >> 48
	t4 &= (M >> 4)

	c = mulU64ToU128(a0, b0)

	d = addMulU128(d, a1, b4)
	d = addMulU128(d, a2, b3)
	d = addMulU128(d, a3, b2)
	d = addMulU128(d, a4, b1)

	u0 := d.lo() & M
	d = d.rshift(52)
	u0 = (u0 << 4) | tx

	c = addMulU128(c, u0, R>>4)

	r.n[0] = c.lo() & M
	c = c.rshift(52)

	c = addMulU128(c, a0, b1)
	c = addMulU128(c, a1, b0)

	d = addMulU128(d, a2, b4)
	d = addMulU128(d, a3, b3)
	d = addMulU128(d, a4, b2)

	c = addMulU128(c, R, d.lo()&M)
	d = d.rshift(52)

	r.n[1] = c.lo() & M
	c = c.rshift(52)

	c = addMulU128(c, a0, b2)
	c = addMulU128(c, a1, b1)
	c = addMulU128(c, a2, b0)

	d = addMulU128(d, a3, b4)
	d = addMulU128(d, a4, b3)

	c = addMulU128(c, R, d.lo())
	d = d.rshift(64)

	r.n[2] = c.lo() & M
	c = c.rshift(52)

	c = addMulU128(c, R<<12, d.lo())
	c = addU128(c, t3)

	r.n[3] = c.lo() & M
	c = c.rshift(52)

	r.n[4] = c.lo() + t4

	r.magnitude = 1
	r.normalized = false
}

func TestFieldMulAsmVsPureGo(t *testing.T) {
	// Test with simple values first
	a := FieldElement{n: [5]uint64{1, 0, 0, 0, 0}, magnitude: 1, normalized: true}
	b := FieldElement{n: [5]uint64{2, 0, 0, 0, 0}, magnitude: 1, normalized: true}

	var rAsm, rGo FieldElement

	// Pure Go
	fieldMulPureGo(&rGo, &a, &b)

	// Assembly
	if hasFieldAsm() {
		fieldMulAsm(&rAsm, &a, &b)
		rAsm.magnitude = 1
		rAsm.normalized = false

		t.Logf("a = %v", a.n)
		t.Logf("b = %v", b.n)
		t.Logf("Go result:  %v", rGo.n)
		t.Logf("Asm result: %v", rAsm.n)

		for i := 0; i < 5; i++ {
			if rAsm.n[i] != rGo.n[i] {
				t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
			}
		}
	} else {
		t.Skip("Assembly not available")
	}
}

func TestFieldMulAsmVsPureGoLarger(t *testing.T) {
	// Test with larger values
	a := FieldElement{
		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}
	b := FieldElement{
		n:          [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}

	var rAsm, rGo FieldElement

	// Pure Go
	fieldMulPureGo(&rGo, &a, &b)

	// Assembly
	if hasFieldAsm() {
		fieldMulAsm(&rAsm, &a, &b)
		rAsm.magnitude = 1
		rAsm.normalized = false

		t.Logf("a = %v", a.n)
		t.Logf("b = %v", b.n)
		t.Logf("Go result:  %v", rGo.n)
		t.Logf("Asm result: %v", rAsm.n)

		for i := 0; i < 5; i++ {
			if rAsm.n[i] != rGo.n[i] {
				t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
			}
		}
	} else {
		t.Skip("Assembly not available")
	}
}

func TestFieldSqrAsmVsPureGo(t *testing.T) {
	a := FieldElement{
		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}

	var rAsm, rGo FieldElement

	// Pure Go (a * a)
	fieldMulPureGo(&rGo, &a, &a)

	// Assembly
	if hasFieldAsm() {
		fieldSqrAsm(&rAsm, &a)
		rAsm.magnitude = 1
		rAsm.normalized = false

		t.Logf("a = %v", a.n)
		t.Logf("Go result:  %v", rGo.n)
		t.Logf("Asm result: %v", rAsm.n)

		for i := 0; i < 5; i++ {
			if rAsm.n[i] != rGo.n[i] {
				t.Errorf("limb %d mismatch: asm=%x, go=%x", i, rAsm.n[i], rGo.n[i])
			}
		}
	} else {
		t.Skip("Assembly not available")
	}
}

// BMI2 tests

func TestFieldMulAsmBMI2VsPureGo(t *testing.T) {
	if !hasFieldAsmBMI2() {
		t.Skip("BMI2+ADX assembly not available")
	}

	// Test with simple values first
	a := FieldElement{n: [5]uint64{1, 0, 0, 0, 0}, magnitude: 1, normalized: true}
	b := FieldElement{n: [5]uint64{2, 0, 0, 0, 0}, magnitude: 1, normalized: true}

	var rBMI2, rGo FieldElement

	// Pure Go
	fieldMulPureGo(&rGo, &a, &b)

	// BMI2 Assembly
	fieldMulAsmBMI2(&rBMI2, &a, &b)
	rBMI2.magnitude = 1
	rBMI2.normalized = false

	t.Logf("a = %v", a.n)
	t.Logf("b = %v", b.n)
	t.Logf("Go result:   %v", rGo.n)
	t.Logf("BMI2 result: %v", rBMI2.n)

	for i := 0; i < 5; i++ {
		if rBMI2.n[i] != rGo.n[i] {
			t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
		}
	}
}

func TestFieldMulAsmBMI2VsPureGoLarger(t *testing.T) {
	if !hasFieldAsmBMI2() {
		t.Skip("BMI2+ADX assembly not available")
	}

	// Test with larger values
	a := FieldElement{
		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}
	b := FieldElement{
		n:          [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}

	var rBMI2, rGo FieldElement

	// Pure Go
	fieldMulPureGo(&rGo, &a, &b)

	// BMI2 Assembly
	fieldMulAsmBMI2(&rBMI2, &a, &b)
	rBMI2.magnitude = 1
	rBMI2.normalized = false

	t.Logf("a = %v", a.n)
	t.Logf("b = %v", b.n)
	t.Logf("Go result:   %v", rGo.n)
	t.Logf("BMI2 result: %v", rBMI2.n)

	for i := 0; i < 5; i++ {
		if rBMI2.n[i] != rGo.n[i] {
			t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
		}
	}
}

func TestFieldMulAsmBMI2VsRegularAsm(t *testing.T) {
	if !hasFieldAsmBMI2() {
		t.Skip("BMI2+ADX assembly not available")
	}
	if !hasFieldAsm() {
		t.Skip("Regular assembly not available")
	}

	// Test with larger values
	a := FieldElement{
		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}
	b := FieldElement{
		n:          [5]uint64{0xabcdef1234567890 & 0xFFFFFFFFFFFFF, 0x9876543210fedcba & 0xFFFFFFFFFFFFF, 0xfedcba1234567890 & 0xFFFFFFFFFFFFF, 0x0987654321abcdef & 0xFFFFFFFFFFFFF, 0x0fedcba98765 & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}

	var rBMI2, rAsm FieldElement

	// Regular Assembly
	fieldMulAsm(&rAsm, &a, &b)
	rAsm.magnitude = 1
	rAsm.normalized = false

	// BMI2 Assembly
	fieldMulAsmBMI2(&rBMI2, &a, &b)
	rBMI2.magnitude = 1
	rBMI2.normalized = false

	t.Logf("a = %v", a.n)
	t.Logf("b = %v", b.n)
	t.Logf("Asm result:  %v", rAsm.n)
	t.Logf("BMI2 result: %v", rBMI2.n)

	for i := 0; i < 5; i++ {
		if rBMI2.n[i] != rAsm.n[i] {
			t.Errorf("limb %d mismatch: bmi2=%x, asm=%x", i, rBMI2.n[i], rAsm.n[i])
		}
	}
}

func TestFieldSqrAsmBMI2VsPureGo(t *testing.T) {
	if !hasFieldAsmBMI2() {
		t.Skip("BMI2+ADX assembly not available")
	}

	a := FieldElement{
		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}

	var rBMI2, rGo FieldElement

	// Pure Go (a * a)
	fieldMulPureGo(&rGo, &a, &a)

	// BMI2 Assembly
	fieldSqrAsmBMI2(&rBMI2, &a)
	rBMI2.magnitude = 1
	rBMI2.normalized = false

	t.Logf("a = %v", a.n)
	t.Logf("Go result:   %v", rGo.n)
	t.Logf("BMI2 result: %v", rBMI2.n)

	for i := 0; i < 5; i++ {
		if rBMI2.n[i] != rGo.n[i] {
			t.Errorf("limb %d mismatch: bmi2=%x, go=%x", i, rBMI2.n[i], rGo.n[i])
		}
	}
}

func TestFieldSqrAsmBMI2VsRegularAsm(t *testing.T) {
	if !hasFieldAsmBMI2() {
		t.Skip("BMI2+ADX assembly not available")
	}
	if !hasFieldAsm() {
		t.Skip("Regular assembly not available")
	}

	a := FieldElement{
		n:          [5]uint64{0x1234567890abcdef & 0xFFFFFFFFFFFFF, 0xfedcba9876543210 & 0xFFFFFFFFFFFFF, 0x0123456789abcdef & 0xFFFFFFFFFFFFF, 0xfedcba0987654321 & 0xFFFFFFFFFFFFF, 0x0123456789ab & 0x0FFFFFFFFFFFF},
		magnitude:  1,
		normalized: true,
	}

	var rBMI2, rAsm FieldElement

	// Regular Assembly
	fieldSqrAsm(&rAsm, &a)
	rAsm.magnitude = 1
	rAsm.normalized = false

	// BMI2 Assembly
	fieldSqrAsmBMI2(&rBMI2, &a)
	rBMI2.magnitude = 1
	rBMI2.normalized = false

	t.Logf("a = %v", a.n)
	t.Logf("Asm result:  %v", rAsm.n)
	t.Logf("BMI2 result: %v", rBMI2.n)

	for i := 0; i < 5; i++ {
		if rBMI2.n[i] != rAsm.n[i] {
			t.Errorf("limb %d mismatch: bmi2=%x, asm=%x", i, rBMI2.n[i], rAsm.n[i])
		}
	}
}

// TestFieldMulAsmBMI2Random tests with many random values
func TestFieldMulAsmBMI2Random(t *testing.T) {
	if !hasFieldAsmBMI2() {
		t.Skip("BMI2+ADX assembly not available")
	}
	if !hasFieldAsm() {
		t.Skip("Regular assembly not available")
	}

	// Test with many random values
	for iter := 0; iter < 10000; iter++ {
		var a, b FieldElement
		a.magnitude = 1
		a.normalized = true
		b.magnitude = 1
		b.normalized = true

		// Generate deterministic but varied test data
		seed := uint64(iter * 12345678901234567)
		for j := 0; j < 5; j++ {
			seed = seed*6364136223846793005 + 1442695040888963407 // LCG
			a.n[j] = seed & 0xFFFFFFFFFFFFF

			seed = seed*6364136223846793005 + 1442695040888963407
			b.n[j] = seed & 0xFFFFFFFFFFFFF
		}
		// Limb 4 is only 48 bits
		a.n[4] &= 0x0FFFFFFFFFFFF
		b.n[4] &= 0x0FFFFFFFFFFFF

		var rAsm, rBMI2 FieldElement

		// Regular Assembly
		fieldMulAsm(&rAsm, &a, &b)
		rAsm.magnitude = 1
		rAsm.normalized = false

		// BMI2 Assembly
		fieldMulAsmBMI2(&rBMI2, &a, &b)
		rBMI2.magnitude = 1
		rBMI2.normalized = false

		// Compare results
		for j := 0; j < 5; j++ {
			if rAsm.n[j] != rBMI2.n[j] {
				t.Errorf("Iteration %d: limb %d mismatch", iter, j)
				t.Errorf("  a = %v", a.n)
				t.Errorf("  b = %v", b.n)
				t.Errorf("  Asm:  %v", rAsm.n)
				t.Errorf("  BMI2: %v", rBMI2.n)
				return
			}
		}
	}
}

// TestFieldSqrAsmBMI2Random tests squaring with many random values
func TestFieldSqrAsmBMI2Random(t *testing.T) {
	if !hasFieldAsmBMI2() {
		t.Skip("BMI2+ADX assembly not available")
	}
	if !hasFieldAsm() {
		t.Skip("Regular assembly not available")
	}

	// Test with many random values
	for iter := 0; iter < 10000; iter++ {
		var a FieldElement
		a.magnitude = 1
		a.normalized = true

		// Generate deterministic but varied test data
		seed := uint64(iter * 98765432109876543)
		for j := 0; j < 5; j++ {
			seed = seed*6364136223846793005 + 1442695040888963407 // LCG
			a.n[j] = seed & 0xFFFFFFFFFFFFF
		}
		// Limb 4 is only 48 bits
		a.n[4] &= 0x0FFFFFFFFFFFF

		var rAsm, rBMI2 FieldElement

		// Regular Assembly
		fieldSqrAsm(&rAsm, &a)
		rAsm.magnitude = 1
		rAsm.normalized = false

		// BMI2 Assembly
		fieldSqrAsmBMI2(&rBMI2, &a)
		rBMI2.magnitude = 1
		rBMI2.normalized = false

		// Compare results
		for j := 0; j < 5; j++ {
			if rAsm.n[j] != rBMI2.n[j] {
				t.Errorf("Iteration %d: limb %d mismatch", iter, j)
				t.Errorf("  a = %v", a.n)
				t.Errorf("  Asm:  %v", rAsm.n)
				t.Errorf("  BMI2: %v", rBMI2.n)
				return
			}
		}
	}
}
