package authn

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

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

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

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

	fields := RequestSigningFields{
		ProtocolVersion: "v1",
		DeviceSessionID: "device-session-123",
		MessageType:     "fleet.move",
		TimestampMS:     123456789,
		RequestID:       "request-123",
		PayloadHash:     mustSHA256([]byte("payload")),
	}

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

	tests := []struct {
		name            string
		clientPublicKey string
		signature       []byte
		fields          RequestSigningFields
		wantErr         error
	}{
		{
			name:            "valid signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields:          fields,
		},
		{
			name:            "message type change rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields: func() RequestSigningFields {
				mutated := fields
				mutated.MessageType = "fleet.attack"
				return mutated
			}(),
			wantErr: ErrInvalidRequestSignature,
		},
		{
			name:            "request id change rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields: func() RequestSigningFields {
				mutated := fields
				mutated.RequestID = "request-456"
				return mutated
			}(),
			wantErr: ErrInvalidRequestSignature,
		},
		{
			name:            "payload hash change rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature,
			fields: func() RequestSigningFields {
				mutated := fields
				mutated.PayloadHash = mustSHA256([]byte("other"))
				return mutated
			}(),
			wantErr: ErrInvalidRequestSignature,
		},
		{
			name:            "wrong key rejects signature",
			clientPublicKey: base64.StdEncoding.EncodeToString(otherPrivateKey.Public().(ed25519.PublicKey)),
			signature:       signature,
			fields:          fields,
			wantErr:         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: ErrInvalidRequestSignature,
		},
		{
			name:            "invalid signature length rejects",
			clientPublicKey: base64.StdEncoding.EncodeToString(clientPublicKey),
			signature:       signature[:len(signature)-1],
			fields:          fields,
			wantErr:         ErrInvalidRequestSignature,
		},
		{
			name:            "invalid base64 public key rejects",
			clientPublicKey: "%%%not-base64%%%",
			signature:       signature,
			fields:          fields,
			wantErr:         ErrInvalidClientPublicKey,
		},
		{
			name:            "invalid public key length rejects",
			clientPublicKey: base64.StdEncoding.EncodeToString([]byte("short")),
			signature:       signature,
			fields:          fields,
			wantErr:         ErrInvalidClientPublicKey,
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			err := 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("gateway-authn-signature-test-" + label))
	return ed25519.NewKeyFromSeed(seed[:])
}
