package identity_test

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"testing"

	"github.com/drjones/quantum-arcade/pkg/identity"
)

func newKey(t *testing.T) (string, ed25519.PrivateKey) {
	t.Helper()
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	return hex.EncodeToString(pub), priv
}

func TestValidSignatureAuthenticates(t *testing.T) {
	a := identity.NewAuthenticator()
	pubHex, priv := newKey(t)

	nonceHex, err := a.Challenge(pubHex)
	if err != nil {
		t.Fatal(err)
	}
	nonce, _ := hex.DecodeString(nonceHex)
	sig := ed25519.Sign(priv, nonce)

	if err := a.Verify(pubHex, hex.EncodeToString(sig)); err != nil {
		t.Fatalf("valid signature rejected: %v", err)
	}
}

func TestWrongKeyCannotAuthenticate(t *testing.T) {
	a := identity.NewAuthenticator()
	pubHex, _ := newKey(t)
	_, otherPriv := newKey(t)

	nonceHex, _ := a.Challenge(pubHex)
	nonce, _ := hex.DecodeString(nonceHex)
	sig := ed25519.Sign(otherPriv, nonce)

	if err := a.Verify(pubHex, hex.EncodeToString(sig)); !errors.Is(err, identity.ErrBadSignature) {
		t.Fatalf("got %v, want ErrBadSignature", err)
	}
}

// A captured signature must not work twice.
func TestChallengeIsSingleUse(t *testing.T) {
	a := identity.NewAuthenticator()
	pubHex, priv := newKey(t)

	nonceHex, _ := a.Challenge(pubHex)
	nonce, _ := hex.DecodeString(nonceHex)
	sigHex := hex.EncodeToString(ed25519.Sign(priv, nonce))

	if err := a.Verify(pubHex, sigHex); err != nil {
		t.Fatal(err)
	}
	if err := a.Verify(pubHex, sigHex); !errors.Is(err, identity.ErrUnknownChallenge) {
		t.Fatalf("replay succeeded or gave %v, want ErrUnknownChallenge", err)
	}
}

func TestMalformedKeyRejected(t *testing.T) {
	a := identity.NewAuthenticator()
	if _, err := a.Challenge("not-hex"); !errors.Is(err, identity.ErrBadPublicKey) {
		t.Fatalf("got %v, want ErrBadPublicKey", err)
	}
	if _, err := a.Challenge("aabb"); !errors.Is(err, identity.ErrBadPublicKey) {
		t.Fatalf("short key: got %v, want ErrBadPublicKey", err)
	}
}

func TestVerifyWithoutChallengeFails(t *testing.T) {
	a := identity.NewAuthenticator()
	pubHex, priv := newKey(t)
	sig := ed25519.Sign(priv, []byte("anything"))
	if err := a.Verify(pubHex, hex.EncodeToString(sig)); !errors.Is(err, identity.ErrUnknownChallenge) {
		t.Fatalf("got %v, want ErrUnknownChallenge", err)
	}
}
