package keypair_test

import (
	"bytes"
	"crypto/ed25519"
	"crypto/rand"
	"encoding/base64"
	"testing"

	"galaxy/core/keypair"

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

func TestGenerateProducesIndependentCopies(t *testing.T) {
	t.Parallel()

	priv, pub, err := keypair.Generate(rand.Reader)
	require.NoError(t, err)
	require.Len(t, priv, ed25519.PrivateKeySize)
	require.Len(t, pub, ed25519.PublicKeySize)

	// Mutating the returned slices must not affect a fresh call.
	priv[0] ^= 0xFF
	pub[0] ^= 0xFF
	priv2, pub2, err := keypair.Generate(rand.Reader)
	require.NoError(t, err)
	assert.NotEqual(t, priv[:8], priv2[:8])
	assert.NotEqual(t, pub[:8], pub2[:8])
}

func TestGenerateIsDeterministicForFixedSeed(t *testing.T) {
	t.Parallel()

	seed := bytes.Repeat([]byte{0x42}, ed25519.SeedSize)

	priv1, pub1, err := keypair.Generate(bytes.NewReader(seed))
	require.NoError(t, err)
	priv2, pub2, err := keypair.Generate(bytes.NewReader(seed))
	require.NoError(t, err)

	assert.Equal(t, priv1, priv2)
	assert.Equal(t, pub1, pub2)
}

func TestGenerateRejectsNilReader(t *testing.T) {
	t.Parallel()

	_, _, err := keypair.Generate(nil)
	require.Error(t, err)
}

func TestSignRoundTrip(t *testing.T) {
	t.Parallel()

	priv, pub, err := keypair.Generate(rand.Reader)
	require.NoError(t, err)

	message := []byte("ui-core-roundtrip")
	signature, err := keypair.Sign(priv, message)
	require.NoError(t, err)
	assert.Len(t, signature, ed25519.SignatureSize)

	assert.True(t, keypair.Verify(pub, message, signature))
	assert.False(t, keypair.Verify(pub, []byte("tampered"), signature))
	tampered := append([]byte(nil), signature...)
	tampered[0] ^= 0xFF
	assert.False(t, keypair.Verify(pub, message, tampered))
}

func TestSignRejectsInvalidPrivateKey(t *testing.T) {
	t.Parallel()

	_, err := keypair.Sign([]byte("short"), []byte("message"))
	require.ErrorIs(t, err, keypair.ErrInvalidPrivateKey)
}

func TestVerifyRejectsInvalidLengths(t *testing.T) {
	t.Parallel()

	priv, pub, err := keypair.Generate(rand.Reader)
	require.NoError(t, err)
	signature, err := keypair.Sign(priv, []byte("message"))
	require.NoError(t, err)

	assert.False(t, keypair.Verify(pub[:8], []byte("message"), signature))
	assert.False(t, keypair.Verify(pub, []byte("message"), signature[:8]))
}

func TestMarshalUnmarshalPublicKeyRoundTrip(t *testing.T) {
	t.Parallel()

	_, pub, err := keypair.Generate(rand.Reader)
	require.NoError(t, err)

	encoded, err := keypair.MarshalPublicKey(pub)
	require.NoError(t, err)
	require.NotEmpty(t, encoded)

	// Encoding must be base64 StdEncoding to match docs/ARCHITECTURE.md §15.
	expected := base64.StdEncoding.EncodeToString(pub)
	assert.Equal(t, expected, encoded)

	decoded, err := keypair.UnmarshalPublicKey(encoded)
	require.NoError(t, err)
	assert.Equal(t, pub, decoded)
}

func TestMarshalPublicKeyRejectsInvalidLength(t *testing.T) {
	t.Parallel()

	_, err := keypair.MarshalPublicKey([]byte("short"))
	require.ErrorIs(t, err, keypair.ErrInvalidPublicKey)
}

func TestUnmarshalPublicKeyRejectsBadEncoding(t *testing.T) {
	t.Parallel()

	_, err := keypair.UnmarshalPublicKey("%%%not-base64%%%")
	require.ErrorIs(t, err, keypair.ErrInvalidPublicKeyEncoding)
}

func TestUnmarshalPublicKeyRejectsWrongLength(t *testing.T) {
	t.Parallel()

	_, err := keypair.UnmarshalPublicKey(base64.StdEncoding.EncodeToString([]byte("short")))
	require.ErrorIs(t, err, keypair.ErrInvalidPublicKey)
}

func TestPublicKeyFromPrivate(t *testing.T) {
	t.Parallel()

	priv, pub, err := keypair.Generate(rand.Reader)
	require.NoError(t, err)

	derived, err := keypair.PublicKeyFromPrivate(priv)
	require.NoError(t, err)
	assert.Equal(t, pub, derived)

	_, err = keypair.PublicKeyFromPrivate([]byte("short"))
	require.ErrorIs(t, err, keypair.ErrInvalidPrivateKey)
}
