package connector_oauth

import (
	"errors"
	"strings"
	"testing"
)

func TestAESGCMCipherBindsPurposeOwnerRowAndGeneration(t *testing.T) {
	key := []byte("0123456789abcdef0123456789abcdef")
	cipher, err := NewAESGCMCipher("key-a", map[string][]byte{"key-a": key})
	if err != nil {
		t.Fatal(err)
	}
	base := SecretContext{Purpose: SecretPrincipalAccessToken, UserID: "user-1", ConnectorKey: "provider-a", RecordID: "principal-1", Generation: 7}
	sealed, keyID, err := cipher.Seal([]byte("access-secret-value"), base)
	if err != nil {
		t.Fatal(err)
	}
	if keyID != "key-a" || strings.Contains(sealed, "access-secret-value") {
		t.Fatalf("envelope key/plaintext = %q/%q", keyID, sealed)
	}
	plaintext, err := cipher.Open(sealed, base)
	if err != nil || string(plaintext) != "access-secret-value" {
		t.Fatalf("Open = %q, %v", plaintext, err)
	}

	mutations := []SecretContext{
		{Purpose: SecretPrincipalRefreshToken, UserID: base.UserID, ConnectorKey: base.ConnectorKey, RecordID: base.RecordID, Generation: base.Generation},
		{Purpose: base.Purpose, UserID: "user-2", ConnectorKey: base.ConnectorKey, RecordID: base.RecordID, Generation: base.Generation},
		{Purpose: base.Purpose, UserID: base.UserID, ConnectorKey: "provider-b", RecordID: base.RecordID, Generation: base.Generation},
		{Purpose: base.Purpose, UserID: base.UserID, ConnectorKey: base.ConnectorKey, RecordID: "principal-2", Generation: base.Generation},
		{Purpose: base.Purpose, UserID: base.UserID, ConnectorKey: base.ConnectorKey, RecordID: base.RecordID, Generation: base.Generation + 1},
	}
	for _, mutation := range mutations {
		if _, err := cipher.Open(sealed, mutation); !errors.Is(err, ErrCiphertextInvalid) {
			t.Fatalf("context swap %+v error = %v", mutation, err)
		}
	}
}

func TestAESGCMCipherDecryptsOldKeyAndEncryptsWithActiveKey(t *testing.T) {
	oldKey := []byte("0123456789abcdef0123456789abcdef")
	newKey := []byte("abcdef0123456789abcdef0123456789")
	secretContext := SecretContext{Purpose: SecretAuthorizationVerifier, UserID: "user-1", ConnectorKey: "provider-a", RecordID: "request-1", Generation: 1}
	oldCipher, _ := NewAESGCMCipher("old", map[string][]byte{"old": oldKey})
	oldEnvelope, _, err := oldCipher.Seal([]byte("proof-key"), secretContext)
	if err != nil {
		t.Fatal(err)
	}
	rotated, _ := NewAESGCMCipher("new", map[string][]byte{"old": oldKey, "new": newKey})
	plaintext, err := rotated.Open(oldEnvelope, secretContext)
	if err != nil || string(plaintext) != "proof-key" {
		t.Fatalf("old key open = %q, %v", plaintext, err)
	}
	newEnvelope, keyID, err := rotated.Seal([]byte("proof-key"), secretContext)
	if err != nil || keyID != "new" || !strings.HasPrefix(newEnvelope, "v1.new.") {
		t.Fatalf("new envelope = %q key=%q err=%v", newEnvelope, keyID, err)
	}
}

func TestAESGCMCipherKeyIDWireFormat(t *testing.T) {
	key := []byte("0123456789abcdef0123456789abcdef")
	secretContext := SecretContext{Purpose: SecretPrincipalAccessToken, UserID: "user-1", ConnectorKey: "provider-a", RecordID: "principal-1", Generation: 1}

	accepted, err := NewAESGCMCipher("key-v2_3", map[string][]byte{"key-v2_3": key})
	if err != nil {
		t.Fatalf("accepted key ID: %v", err)
	}
	envelope, _, err := accepted.Seal([]byte("secret"), secretContext)
	if err != nil {
		t.Fatal(err)
	}
	plaintext, err := accepted.Open(envelope, secretContext)
	if err != nil || string(plaintext) != "secret" {
		t.Fatalf("accepted key ID round trip = %q, %v", plaintext, err)
	}

	for name, keys := range map[string]map[string][]byte{
		"active delimiter":  {"key.v2": key},
		"old-key delimiter": {"key-v2": key, "key.v1": key},
	} {
		t.Run(name, func(t *testing.T) {
			activeKeyID := "key-v2"
			if name == "active delimiter" {
				activeKeyID = "key.v2"
			}
			if _, err := NewAESGCMCipher(activeKeyID, keys); !errors.Is(err, ErrEncryptionKeyMissing) {
				t.Fatalf("rejected key ID error = %v", err)
			}
		})
	}
}

func TestProviderErrorsAreAllowlistedAndRedacted(t *testing.T) {
	secret := "access-secret refresh-secret code-secret verifier-secret Authorization: Bearer"
	err := safeProviderError("refresh", errors.New(secret))
	if got := err.Error(); got != "connected provider refresh failed: provider_error" || strings.Contains(got, "secret") || strings.Contains(got, "Bearer") {
		t.Fatalf("redacted error = %q", got)
	}
	var refreshErr *ProviderError
	if !errors.As(err, &refreshErr) || refreshErr.Kind != ProviderErrorUncertain {
		t.Fatalf("unclassified refresh error = %#v", err)
	}
	allowed := safeProviderError("exchange", &ProviderError{Operation: "ignored", Kind: ProviderErrorAuthorization, Code: "invalid_grant"})
	if got := allowed.Error(); got != "connected provider exchange failed: invalid_grant" {
		t.Fatalf("allowlisted error = %q", got)
	}
	var typedNil *ProviderError
	typedNilErr := safeProviderError("refresh", typedNil)
	if got := typedNilErr.Error(); got != "connected provider refresh failed: provider_error" {
		t.Fatalf("typed-nil error = %q", got)
	}
}
