package canon_test

import (
	"crypto/ed25519"
	"crypto/sha256"
	"encoding/base64"
	"testing"

	"galaxy/core/canon"

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

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

	clientPrivateKey := newTestPrivateKey("primary")
	clientPublicKey, _ := clientPrivateKey.Public().(ed25519.PublicKey)
	otherPrivateKey := newTestPrivateKey("other")
	otherPublicKey, _ := otherPrivateKey.Public().(ed25519.PublicKey)

	fields := canon.RequestSigningFields{
		ProtocolVersion: "v1",
		DeviceSessionID: "device-session-123",
		MessageType:     "user.games.command",
		TimestampMS:     123456789,
		RequestID:       "request-123",
		PayloadHash:     sha256Sum([]byte("payload")),
	}

	signature := ed25519.Sign(clientPrivateKey, canon.BuildRequestSigningInput(fields))

	tests := []struct {
		name            string
		clientPublicKey string
		signature       []byte
		fields          canon.RequestSigningFields
		wantErr         error
	}{
		{
			name:            "valid signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields:          fields,
		},
		{
			name:            "tampered payload hash rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields: func() canon.RequestSigningFields {
				mutated := fields
				mutated.PayloadHash = sha256Sum([]byte("other"))
				return mutated
			}(),
			wantErr: canon.ErrInvalidRequestSignature,
		},
		{
			name:            "mismatched request id rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields: func() canon.RequestSigningFields {
				mutated := fields
				mutated.RequestID = "request-456"
				return mutated
			}(),
			wantErr: canon.ErrInvalidRequestSignature,
		},
		{
			name:            "mismatched timestamp rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields: func() canon.RequestSigningFields {
				mutated := fields
				mutated.TimestampMS++
				return mutated
			}(),
			wantErr: canon.ErrInvalidRequestSignature,
		},
		{
			name:            "wrong key rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(otherPublicKey),
			signature:       signature,
			fields:          fields,
			wantErr:         canon.ErrInvalidRequestSignature,
		},
		{
			name:            "bit-flipped signature rejects",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature: func() []byte {
				corrupted := append([]byte(nil), signature...)
				corrupted[0] ^= 0xFF
				return corrupted
			}(),
			fields:  fields,
			wantErr: canon.ErrInvalidRequestSignature,
		},
		{
			name:            "invalid signature length rejects",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature[:len(signature)-1],
			fields:          fields,
			wantErr:         canon.ErrInvalidRequestSignature,
		},
		{
			name:            "empty signature rejects",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       nil,
			fields:          fields,
			wantErr:         canon.ErrInvalidRequestSignature,
		},
		{
			name:            "invalid base64 public key rejects",
			clientPublicKey: "%%%not-base64%%%",
			signature:       signature,
			fields:          fields,
			wantErr:         canon.ErrInvalidClientPublicKey,
		},
		{
			name:            "invalid public key length rejects",
			clientPublicKey: base64.StdEncoding.EncodeToString([]byte("short")),
			signature:       signature,
			fields:          fields,
			wantErr:         canon.ErrInvalidClientPublicKey,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			err := canon.VerifyRequestSignature(tt.clientPublicKey, tt.signature, tt.fields)
			if tt.wantErr == nil {
				require.NoError(t, err)
				return
			}

			require.ErrorIs(t, err, tt.wantErr)
		})
	}
}

func newTestPrivateKey(label string) ed25519.PrivateKey {
	seed := sha256.Sum256([]byte("ui-core-canon-signature-test-" + label))
	return ed25519.NewKeyFromSeed(seed[:])
}
