package capabilities

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"strings"
	"testing"
	"time"

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

func validClaims(now time.Time) Claims {
	return Claims{
		Subject:       " member-1 ",
		AppID:         " app-1 ",
		WorkspaceID:   " workspace-1 ",
		MemberID:      " member-1 ",
		Scopes:        []string{" messages:write ", "messages:write", "channels:read", " "},
		TokenID:       " token-1 ",
		RequestID:     " request-1 ",
		IssuedAtUnix:  now.Unix(),
		ExpiresAtUnix: now.Add(time.Hour).Unix(),
	}
}

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

	now := time.Now().UTC()
	signer, err := NewSigner([]byte("test-signing-key"))
	require.NoError(t, err)

	token, err := signer.Sign(validClaims(now))
	require.NoError(t, err)
	require.Len(t, strings.Split(token, "."), 3)

	claims, err := signer.Verify(token)
	require.NoError(t, err)
	require.Equal(t, Issuer(), claims.Issuer)
	require.Equal(t, Audience(), claims.Audience)
	require.Equal(t, "member-1", claims.Subject)
	require.Equal(t, "app-1", claims.AppID)
	require.Equal(t, []string{"messages:write", "channels:read"}, claims.Scopes)
	require.Equal(t, "request-1", claims.RequestID)
}

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

	_, err := NewSigner(nil)
	require.ErrorContains(t, err, "signing key is required")

	signer, err := NewSigner([]byte("test-signing-key"))
	require.NoError(t, err)

	claims := validClaims(time.Now().UTC())
	claims.Subject = ""
	_, err = signer.Sign(claims)
	require.ErrorIs(t, err, ErrInvalidToken)

	token, err := signer.Sign(validClaims(time.Now().UTC()))
	require.NoError(t, err)
	_, err = signer.Verify(token + "tampered")
	require.ErrorIs(t, err, ErrInvalidToken)

	otherSigner, err := NewSigner([]byte("other-signing-key"))
	require.NoError(t, err)
	_, err = otherSigner.Verify(token)
	require.ErrorIs(t, err, ErrInvalidToken)
}

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

	signer, err := NewSigner([]byte("test-signing-key"))
	require.NoError(t, err)
	claims := validClaims(time.Now().UTC().Add(-2 * time.Hour))
	claims.ExpiresAtUnix = time.Now().UTC().Add(-time.Hour).Unix()
	token, err := signer.Sign(claims)
	require.NoError(t, err)

	_, err = signer.Verify(token)
	require.True(t, errors.Is(err, ErrExpiredToken), "err=%v", err)
}

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

	signer, err := NewSigner([]byte("test-signing-key"))
	require.NoError(t, err)

	for _, tt := range []struct {
		name   string
		claims Claims
	}{
		{
			name: "missing_issuer",
			claims: func() Claims {
				claims := validClaims(time.Now().UTC())
				claims.Audience = Audience()
				return claims
			}(),
		},
		{
			name: "missing_audience",
			claims: func() Claims {
				claims := validClaims(time.Now().UTC())
				claims.Issuer = Issuer()
				return claims
			}(),
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			token := signRawClaimsForTest(t, signer, tt.claims)
			_, err := signer.Verify(token)
			require.ErrorIs(t, err, ErrInvalidToken)
		})
	}
}

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

	key := []byte("test-signing-key")
	signer, err := NewSigner(key)
	require.NoError(t, err)
	key[0] = 'X'

	token, err := signer.Sign(validClaims(time.Now().UTC()))
	require.NoError(t, err)
	_, err = signer.Verify(token)
	require.NoError(t, err)
}

func signRawClaimsForTest(t *testing.T, signer *Signer, claims Claims) string {
	t.Helper()

	headerJSON, err := json.Marshal(map[string]string{
		"alg": "HS256",
		"typ": "JWT",
	})
	require.NoError(t, err)
	payloadJSON, err := json.Marshal(claims)
	require.NoError(t, err)

	headerSegment := base64.RawURLEncoding.EncodeToString(headerJSON)
	payloadSegment := base64.RawURLEncoding.EncodeToString(payloadJSON)
	signingInput := headerSegment + "." + payloadSegment
	signature := signer.signature(signingInput)
	return signingInput + "." + base64.RawURLEncoding.EncodeToString(signature)
}
