package codec

import (
	"math/big"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestDiffieHellman(t *testing.T) {
	key1, err := GenerateKey()
	assert.Nil(t, err)
	key2, err := GenerateKey()
	assert.Nil(t, err)

	pubKey1, err := ComputeKey(key1.PubKey, key2.PriKey)
	assert.Nil(t, err)
	pubKey2, err := ComputeKey(key2.PubKey, key1.PriKey)
	assert.Nil(t, err)

	assert.Equal(t, pubKey1, pubKey2)
}

func TestDiffieHellman1024(t *testing.T) {
	old := p
	p, _ = new(big.Int).SetString("F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D334FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E92F78C7", 16)
	defer func() {
		p = old
	}()

	key1, err := GenerateKey()
	assert.Nil(t, err)
	key2, err := GenerateKey()
	assert.Nil(t, err)

	pubKey1, err := ComputeKey(key1.PubKey, key2.PriKey)
	assert.Nil(t, err)
	pubKey2, err := ComputeKey(key2.PubKey, key1.PriKey)
	assert.Nil(t, err)

	assert.Equal(t, pubKey1, pubKey2)
}

func TestDiffieHellmanMiddleManAttack(t *testing.T) {
	key1, err := GenerateKey()
	assert.Nil(t, err)
	keyMiddle, err := GenerateKey()
	assert.Nil(t, err)
	key2, err := GenerateKey()
	assert.Nil(t, err)

	const aesByteLen = 32
	pubKey1, err := ComputeKey(keyMiddle.PubKey, key1.PriKey)
	assert.Nil(t, err)
	src := []byte(`hello, world!`)
	encryptedSrc, err := EcbEncrypt(pubKey1.Bytes()[:aesByteLen], src)
	assert.Nil(t, err)
	pubKeyMiddle, err := ComputeKey(key1.PubKey, keyMiddle.PriKey)
	assert.Nil(t, err)
	decryptedSrc, err := EcbDecrypt(pubKeyMiddle.Bytes()[:aesByteLen], encryptedSrc)
	assert.Nil(t, err)
	assert.Equal(t, string(src), string(decryptedSrc))

	pubKeyMiddle, err = ComputeKey(key2.PubKey, keyMiddle.PriKey)
	assert.Nil(t, err)
	encryptedSrc, err = EcbEncrypt(pubKeyMiddle.Bytes()[:aesByteLen], decryptedSrc)
	assert.Nil(t, err)
	pubKey2, err := ComputeKey(keyMiddle.PubKey, key2.PriKey)
	assert.Nil(t, err)
	decryptedSrc, err = EcbDecrypt(pubKey2.Bytes()[:aesByteLen], encryptedSrc)
	assert.Nil(t, err)
	assert.Equal(t, string(src), string(decryptedSrc))
}

func TestKeyBytes(t *testing.T) {
	var empty DhKey
	assert.Equal(t, 0, len(empty.Bytes()))

	key, err := GenerateKey()
	assert.Nil(t, err)
	assert.True(t, len(key.Bytes()) > 0)
}

func TestDHOnErrors(t *testing.T) {
	key, err := GenerateKey()
	assert.Nil(t, err)
	assert.NotEmpty(t, key.Bytes())
	_, err = ComputeKey(key.PubKey, key.PriKey)
	assert.NoError(t, err)
	_, err = ComputeKey(nil, key.PriKey)
	assert.Error(t, err)
	_, err = ComputeKey(key.PubKey, nil)
	assert.Error(t, err)

	assert.NotNil(t, NewPublicKey([]byte("")))
}

func TestDHPubKeyBoundary(t *testing.T) {
	key, err := GenerateKey()
	assert.Nil(t, err)

	// pubKey = 0 should be rejected
	_, err = ComputeKey(big.NewInt(0), key.PriKey)
	assert.ErrorIs(t, err, ErrPubKeyOutOfBound)

	// pubKey = -1 should be rejected
	_, err = ComputeKey(big.NewInt(-1), key.PriKey)
	assert.ErrorIs(t, err, ErrPubKeyOutOfBound)

	// pubKey = p should be rejected
	_, err = ComputeKey(new(big.Int).Set(p), key.PriKey)
	assert.ErrorIs(t, err, ErrPubKeyOutOfBound)

	// pubKey = p+1 should be rejected
	_, err = ComputeKey(new(big.Int).Add(p, big.NewInt(1)), key.PriKey)
	assert.ErrorIs(t, err, ErrPubKeyOutOfBound)

	// pubKey = 1 should be accepted
	_, err = ComputeKey(big.NewInt(1), key.PriKey)
	assert.NoError(t, err)

	// pubKey = p-1 should be accepted
	_, err = ComputeKey(new(big.Int).Sub(p, big.NewInt(1)), key.PriKey)
	assert.NoError(t, err)
}
