package identity

import (
	"context"
	"encoding/json"
	"errors"
	"strings"
	"sync"
	"testing"
	"time"
)

type pairingRepositoryFake struct {
	revision                   Revision
	exchange                   PairingExchange
	blocked                    bool
	startReservationErr        error
	startReservationID         string
	startReservationState      string
	startReservationCalls      int
	startReservationReleases   int
	pendingSecretCleanups      map[string]time.Time
	commitStartErr             error
	commitStartAmbiguous       bool
	applyManifestAmbiguousOnce bool
	failCredentialsSavedOnce   bool
}

func (f *pairingRepositoryFake) ReserveIdentityPairingStart(_ context.Context, attemptID string, _ time.Time) error {
	f.startReservationCalls++
	if f.startReservationErr != nil {
		return f.startReservationErr
	}
	if f.blocked || f.startReservationID != "" {
		return ErrPairingInProgress
	}
	f.startReservationID = attemptID
	f.startReservationState = "starting"
	return nil
}
func (f *pairingRepositoryFake) ReleaseIdentityPairingStart(_ context.Context, attemptID string) error {
	if f.startReservationID == attemptID && f.startReservationState == "starting" {
		f.startReservationID = ""
		f.startReservationState = ""
		f.startReservationReleases++
	}
	return nil
}
func (f *pairingRepositoryFake) CommitIdentityPairingStart(_ context.Context, revision Revision, exchange PairingExchange) (PairingExchange, error) {
	if f.commitStartErr != nil && !f.commitStartAmbiguous {
		return PairingExchange{}, f.commitStartErr
	}
	if f.startReservationID != exchange.ID || f.startReservationState != "starting" {
		return PairingExchange{}, ErrPairingInProgress
	}
	if _, staged := f.pendingSecretCleanups[exchange.ExchangeTokenRef]; !staged {
		return PairingExchange{}, errors.New("exchange token was not staged")
	}
	delete(f.pendingSecretCleanups, exchange.ExchangeTokenRef)
	f.revision = revision
	f.exchange = exchange
	f.startReservationState = "paired"
	if f.commitStartAmbiguous {
		return PairingExchange{}, f.commitStartErr
	}
	return exchange, nil
}

func (f *pairingRepositoryFake) CreateIdentityConfigurationRevision(_ context.Context, revision Revision) (Revision, error) {
	f.revision = revision
	return revision, nil
}
func (f *pairingRepositoryFake) IdentityConfigurationRevision(context.Context, string) (Revision, error) {
	return f.revision, nil
}
func (f *pairingRepositoryFake) ApplyIdentityManifest(_ context.Context, _ string, _ int64, applied ManifestApplication) (Revision, error) {
	for _, reference := range []string{applied.MachineCredentialRef, applied.SessionEncryptionKeyRef} {
		if reference == "" {
			continue
		}
		if _, staged := f.pendingSecretCleanups[reference]; !staged {
			return Revision{}, errors.New("identity secret was not staged")
		}
	}
	oldMachineReference, oldSessionReference := f.revision.MachineCredentialRef, f.revision.SessionEncryptionKeyRef
	revision, err := ApplyManifest(f.revision, applied)
	if err != nil {
		return Revision{}, err
	}
	revision.Version++
	f.revision = revision
	for _, reference := range []string{applied.MachineCredentialRef, applied.SessionEncryptionKeyRef} {
		delete(f.pendingSecretCleanups, reference)
	}
	for _, reference := range []string{oldMachineReference, oldSessionReference} {
		if reference != "" && reference != applied.MachineCredentialRef && reference != applied.SessionEncryptionKeyRef {
			f.pendingSecretCleanups[reference] = time.Now()
		}
	}
	if f.applyManifestAmbiguousOnce {
		f.applyManifestAmbiguousOnce = false
		return Revision{}, errors.New("manifest commit response lost")
	}
	return revision, nil
}
func (f *pairingRepositoryFake) CreateIdentityPairingExchange(_ context.Context, exchange PairingExchange) (PairingExchange, error) {
	f.exchange = exchange
	return exchange, nil
}
func (f *pairingRepositoryFake) IdentityPairingExchange(context.Context, string) (PairingExchange, error) {
	return f.exchange, nil
}
func (f *pairingRepositoryFake) UpdateIdentityPairingExchange(_ context.Context, id string, expected int64, update PairingExchangeUpdate) (PairingExchange, error) {
	if f.exchange.ID != id || f.exchange.Version != expected {
		return PairingExchange{}, ErrRevisionConflict
	}
	if update.Status == PairingCredentialsSaved && f.failCredentialsSavedOnce {
		f.failCredentialsSavedOnce = false
		return PairingExchange{}, errors.New("pairing status temporarily unavailable")
	}
	f.exchange.Status, f.exchange.RemoteVersion = update.Status, update.RemoteVersion
	f.exchange.LastErrorCategory, f.exchange.AuthCenterAuditID = update.LastErrorCategory, update.AuthCenterAuditID
	f.exchange.Version++
	if update.Status == PairingCompleted || update.Status == PairingFailed || update.Status == PairingExpired {
		if f.pendingSecretCleanups == nil {
			f.pendingSecretCleanups = map[string]time.Time{}
		}
		f.pendingSecretCleanups[f.exchange.ExchangeTokenRef] = time.Now()
	}
	return f.exchange, nil
}
func (f *pairingRepositoryFake) RecordIdentityPairingRetryFailure(_ context.Context, id string, status PairingStatus, category string) (PairingExchange, error) {
	if f.exchange.ID != id || f.exchange.Status != status {
		return PairingExchange{}, ErrRevisionConflict
	}
	f.exchange.LastErrorCategory = category
	return f.exchange, nil
}
func (f *pairingRepositoryFake) IdentityPairingStartBlocked(context.Context) (bool, error) {
	return f.blocked, nil
}
func (f *pairingRepositoryFake) QueueIdentitySecretCleanup(_ context.Context, reference string, notBefore time.Time) error {
	if f.pendingSecretCleanups == nil {
		f.pendingSecretCleanups = map[string]time.Time{}
	}
	current, exists := f.pendingSecretCleanups[reference]
	if !exists || notBefore.Before(current) {
		f.pendingSecretCleanups[reference] = notBefore
	}
	return nil
}
func (f *pairingRepositoryFake) RemovePendingIdentitySecretCleanup(_ context.Context, reference string) (bool, error) {
	if _, exists := f.pendingSecretCleanups[reference]; !exists {
		return false, nil
	}
	delete(f.pendingSecretCleanups, reference)
	return true, nil
}
func (f *pairingRepositoryFake) CancelIdentityPairingExchange(_ context.Context, id string, expected int64, traceID, auditID string) (PairingExchange, error) {
	if f.exchange.ID != id || f.revision.State == RevisionActive || f.revision.State == RevisionSuperseded {
		return PairingExchange{}, ErrRevisionConflict
	}
	if f.exchange.Status == PairingCancelled {
		return f.exchange, nil
	}
	if f.exchange.Version != expected {
		return PairingExchange{}, ErrRevisionConflict
	}
	f.exchange.Status = PairingCancelled
	f.exchange.CleanupStatus = PairingCleanupPending
	f.exchange.Version++
	now := time.Now().UTC()
	f.exchange.CancelledAt = &now
	f.revision.State = RevisionFailed
	f.revision.LastErrorCategory = "pairing_cancelled"
	f.revision.LastTraceID = traceID
	f.revision.LastAuditID = auditID
	f.revision.Version++
	if f.exchange.ExchangeTokenRef != "" {
		if f.pendingSecretCleanups == nil {
			f.pendingSecretCleanups = map[string]time.Time{}
		}
		f.pendingSecretCleanups[f.exchange.ExchangeTokenRef] = time.Now()
	}
	return f.exchange, nil
}
func (f *pairingRepositoryFake) CompleteIdentityPairingCleanup(_ context.Context, id string, expected int64) (PairingExchange, error) {
	if f.exchange.ID != id || f.exchange.Version != expected || f.exchange.Status != PairingCancelled || f.exchange.CleanupStatus != PairingCleanupPending {
		return PairingExchange{}, ErrRevisionConflict
	}
	f.exchange.CleanupStatus = PairingCleanupCompleted
	f.exchange.Version++
	now := time.Now().UTC()
	f.exchange.CleanupCompletedAt = &now
	f.revision.MachineCredentialRef = ""
	f.revision.SessionEncryptionKeyRef = ""
	if f.startReservationState == "paired" && f.startReservationID == f.exchange.ID {
		f.startReservationID = ""
		f.startReservationState = ""
	}
	return f.exchange, nil
}
func (f *pairingRepositoryFake) RecordIdentityPairingCleanupFailure(_ context.Context, id string, category string) (PairingExchange, error) {
	if f.exchange.ID != id || f.exchange.Status != PairingCancelled || f.exchange.CleanupStatus != PairingCleanupPending {
		return PairingExchange{}, ErrRevisionConflict
	}
	f.exchange.LastErrorCategory = category
	return f.exchange, nil
}

type secretStoreFake struct {
	values      map[string][]byte
	failMachine bool
}

func (f *secretStoreFake) Put(_ context.Context, reference string, value []byte) error {
	if f.failMachine && len(reference) > len("identity-machine-") && reference[:len("identity-machine-")] == "identity-machine-" {
		return errors.New("secret store unavailable")
	}
	if f.values == nil {
		f.values = map[string][]byte{}
	}
	f.values[reference] = append([]byte(nil), value...)
	return nil
}
func (f *secretStoreFake) Get(_ context.Context, reference string) ([]byte, error) {
	value, ok := f.values[reference]
	if !ok {
		return nil, errors.New("secret not found")
	}
	return append([]byte(nil), value...), nil
}
func (f *secretStoreFake) Delete(_ context.Context, reference string) error {
	delete(f.values, reference)
	return nil
}

type onboardingRemoteFake struct {
	claimed       ClaimedExchange
	claimErr      error
	claimCalls    int
	view          Exchange
	delivery      CredentialDelivery
	deliveryCalls int
	completed     bool
	completeErr   error
}

func (f *onboardingRemoteFake) Claim(context.Context, string) (ClaimedExchange, error) {
	f.claimCalls++
	return f.claimed, f.claimErr
}
func (f *onboardingRemoteFake) SubmitMetadata(context.Context, ClaimedExchange, ConsumerMetadata, string) (Exchange, error) {
	f.view.Status, f.view.Version = ExchangePreparing, 2
	return f.view, nil
}
func (f *onboardingRemoteFake) Get(context.Context, string, string) (Exchange, error) {
	f.view.Status, f.view.Version = ExchangeReady, 3
	return f.view, nil
}
func (f *onboardingRemoteFake) DeliverCredential(context.Context, Exchange, string, string) (CredentialDelivery, error) {
	f.deliveryCalls++
	f.delivery.MachineCredential.ClientSecret = "rotated-machine-secret-value-000000000000-" + string(rune('0'+f.deliveryCalls))
	f.delivery.Version = int64(3 + f.deliveryCalls)
	return f.delivery, nil
}
func (f *onboardingRemoteFake) Complete(context.Context, string, string, string, int64) error {
	f.completed = true
	return f.completeErr
}

type securityEventPreparerFake struct {
	called               bool
	err                  error
	cleanupCalled        bool
	cleanupErr           error
	conflictRetireCalled bool
	conflictRetireErr    error
	conflictRevisionID   string
}

type blockingSecurityEventPreparer struct {
	prepareStarted chan struct{}
	releasePrepare chan struct{}
	cleanupStarted chan struct{}
	cleanupOnce    sync.Once
}

func (preparer *blockingSecurityEventPreparer) PrepareSecurityEvents(context.Context, Revision, []byte) error {
	close(preparer.prepareStarted)
	<-preparer.releasePrepare
	return nil
}

func (preparer *blockingSecurityEventPreparer) CleanupPreparedSecurityEvents(context.Context, Revision) error {
	preparer.cleanupOnce.Do(func() { close(preparer.cleanupStarted) })
	return nil
}

func (f *securityEventPreparerFake) PrepareSecurityEvents(context.Context, Revision, []byte) error {
	f.called = true
	return f.err
}
func (f *securityEventPreparerFake) CleanupPreparedSecurityEvents(context.Context, Revision) error {
	f.cleanupCalled = true
	return f.cleanupErr
}
func (f *securityEventPreparerFake) RetireConflictingSecurityEvents(_ context.Context, revision Revision) error {
	f.conflictRetireCalled = true
	f.conflictRevisionID = revision.ID
	return f.conflictRetireErr
}

type safeCategoryTestError struct {
	category string
	message  string
}

func (err safeCategoryTestError) Error() string             { return err.message }
func (err safeCategoryTestError) SafeErrorCategory() string { return err.category }

func TestPairingStartReservationSerializesAndRecoversFailedClaims(t *testing.T) {
	input := PairingInput{
		AuthCenterURL: "https://auth.example.com", OnboardingCode: "one-time-code-value",
		PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}

	t.Run("existing pairing blocks before remote claim", func(t *testing.T) {
		repository := &pairingRepositoryFake{blocked: true}
		remote := &onboardingRemoteFake{}
		service := NewPairingService(repository, &secretStoreFake{}, func(string) (OnboardingRemote, error) { return remote, nil }, nil)

		if _, err := service.Start(context.Background(), input, "trace-blocked"); !errors.Is(err, ErrPairingInProgress) {
			t.Fatalf("blocked Start error=%v", err)
		}
		if repository.startReservationCalls != 1 || repository.startReservationReleases != 0 || repository.startReservationID != "" || remote.claimCalls != 0 {
			t.Fatalf("reservation/claim lifecycle calls=%d releases=%d id=%q claims=%d",
				repository.startReservationCalls, repository.startReservationReleases, repository.startReservationID, remote.claimCalls)
		}
	})

	t.Run("active machine credential blocks destructive rotation before remote claim", func(t *testing.T) {
		repository := &pairingRepositoryFake{startReservationErr: ErrActiveConfigurationHandoffRequired}
		remote := &onboardingRemoteFake{}
		service := NewPairingService(repository, &secretStoreFake{}, func(string) (OnboardingRemote, error) { return remote, nil }, nil)

		if _, err := service.Start(context.Background(), input, "trace-active"); !errors.Is(err, ErrActiveConfigurationHandoffRequired) {
			t.Fatalf("active credential guard error=%v", err)
		}
		if remote.claimCalls != 0 {
			t.Fatal("active credential guard ran after the one-time onboarding code was claimed")
		}
	})

	t.Run("remote failure releases database guard", func(t *testing.T) {
		repository := &pairingRepositoryFake{}
		remote := &onboardingRemoteFake{claimErr: errors.New("remote unavailable")}
		service := NewPairingService(repository, &secretStoreFake{}, func(string) (OnboardingRemote, error) { return remote, nil }, nil)

		if _, err := service.Start(context.Background(), input, "trace-failed"); err == nil {
			t.Fatal("remote claim failure was ignored")
		}
		if repository.startReservationCalls != 1 || repository.startReservationReleases != 1 || repository.startReservationID != "" || remote.claimCalls != 1 {
			t.Fatalf("reservation was not released after remote failure: %#v remote=%#v", repository, remote)
		}
	})
}

func TestPairingStartLeavesUncommittedExchangeTokenInDurableCleanupQueue(t *testing.T) {
	repository := &pairingRepositoryFake{commitStartErr: errors.New("database commit unavailable")}
	secrets := &secretStoreFake{values: map[string][]byte{}}
	remote := &onboardingRemoteFake{claimed: ClaimedExchange{
		ExchangeID: "11111111-1111-1111-1111-111111111111", ExchangeToken: "exchange-token-value",
		Version: 1, ExpiresAt: time.Now().Add(time.Hour),
	}}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) { return remote, nil }, nil)

	if _, err := service.Start(context.Background(), PairingInput{
		AuthCenterURL: "https://auth.example.com", OnboardingCode: "one-time-code-value",
		PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}, "trace-uncommitted"); err == nil {
		t.Fatal("failed pairing commit was ignored")
	}
	if len(secrets.values) != 1 || len(repository.pendingSecretCleanups) != 1 || repository.startReservationID != "" || repository.startReservationReleases != 1 {
		t.Fatalf("uncommitted token was not left recoverable: secrets=%d cleanup=%d reservation=%q releases=%d",
			len(secrets.values), len(repository.pendingSecretCleanups), repository.startReservationID, repository.startReservationReleases)
	}
	for reference := range secrets.values {
		if _, queued := repository.pendingSecretCleanups[reference]; !queued {
			t.Fatalf("uncommitted Secret reference %q is not queued for cleanup", reference)
		}
	}
}

func TestPairingStartAmbiguousCommitNeverRequeuesAdoptedExchangeToken(t *testing.T) {
	repository := &pairingRepositoryFake{
		commitStartErr: errors.New("commit response lost"), commitStartAmbiguous: true,
	}
	secrets := &secretStoreFake{values: map[string][]byte{}}
	remote := &onboardingRemoteFake{claimed: ClaimedExchange{
		ExchangeID: "11111111-1111-1111-1111-111111111111", ExchangeToken: "exchange-token-value",
		Version: 1, ExpiresAt: time.Now().Add(time.Hour),
	}}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) { return remote, nil }, nil)

	if _, err := service.Start(context.Background(), PairingInput{
		AuthCenterURL: "https://auth.example.com", OnboardingCode: "one-time-code-value",
		PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}, "trace-ambiguous"); err == nil {
		t.Fatal("ambiguous pairing commit was not reported")
	}
	if repository.exchange.ID == "" || repository.revision.ID == "" {
		t.Fatal("test did not simulate a committed Pairing")
	}
	if len(secrets.values) != 1 || len(repository.pendingSecretCleanups) != 0 {
		t.Fatalf("adopted exchange token was re-queued: secrets=%d cleanup=%d", len(secrets.values), len(repository.pendingSecretCleanups))
	}
	if repository.startReservationID != repository.exchange.ID || repository.startReservationState != "paired" || repository.startReservationReleases != 0 {
		t.Fatalf("committed reservation was not retained: id=%q state=%q releases=%d",
			repository.startReservationID, repository.startReservationState, repository.startReservationReleases)
	}
}

func TestPairingRetriesWithRotatedCredentialAfterSecretStoreFailure(t *testing.T) {
	repository := &pairingRepositoryFake{}
	secrets := &secretStoreFake{values: map[string][]byte{}, failMachine: true}
	remote := &onboardingRemoteFake{
		claimed: ClaimedExchange{ExchangeID: "11111111-1111-1111-1111-111111111111", ExchangeToken: "exchange-token-value", Version: 1, ExpiresAt: time.Now().Add(time.Hour)},
		view:    Exchange{ExchangeID: "11111111-1111-1111-1111-111111111111", ApplicationID: "22222222-2222-2222-2222-222222222222", Version: 1, ExpiresAt: time.Now().Add(time.Hour)},
		delivery: CredentialDelivery{Manifest: ManifestV1{
			SchemaVersion: 1, Issuer: "https://auth.example.com/issuer/shared", TenantID: "33333333-3333-3333-3333-333333333333",
			ApplicationID: "22222222-2222-2222-2222-222222222222", Audience: "urn:easyai:resource:22222222-2222-2222-2222-222222222222",
			Capabilities: []string{"oidc_login", "api_access", "machine_to_machine", "token_introspection", "session_revocation"}, Scopes: []string{"openid", "gateway.access"},
			Clients:        ManifestClients{BrowserLogin: &ManifestClient{ClientID: "browser"}, MachineToMachine: &ManifestClient{ClientID: "service"}},
			SecurityEvents: &ManifestSecurityEvents{TransmitterIssuer: "https://auth.example.com/ssf", ConfigurationEndpoint: "https://auth.example.com/.well-known/ssf-configuration/ssf", Audience: "urn:easyai:ssf:receiver:22222222-2222-2222-2222-222222222222"},
		}, MachineCredential: &MachineCredential{ClientID: "service", ClientSecret: "placeholder", IssuedAt: time.Now()}},
	}
	preparer := &securityEventPreparerFake{}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) { return remote, nil }, preparer)
	pairing, err := service.Start(context.Background(), PairingInput{
		AuthCenterURL: "https://auth.example.com", OnboardingCode: "one-time-code-value",
		PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}, "trace-pairing")
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := secrets.values[pairing.ExchangeTokenRef]; !ok {
		t.Fatal("exchange token was not saved in SecretStore")
	}

	if _, err := service.Continue(context.Background(), pairing.ID); err == nil {
		t.Fatal("machine SecretStore failure was ignored")
	}
	if remote.completed || repository.revision.ApplicationID != "" {
		t.Fatal("failed secret save advanced the exchange or revision")
	}
	secrets.failMachine = false
	completed, err := service.Continue(context.Background(), pairing.ID)
	if err != nil {
		t.Fatal(err)
	}
	if !remote.completed || completed.Status != PairingCompleted || remote.deliveryCalls != 2 || !preparer.called {
		t.Fatalf("pairing did not recover safely: pairing=%#v calls=%d completed=%v", completed, remote.deliveryCalls, remote.completed)
	}
	if repository.revision.MachineCredentialRef == "" || repository.revision.SessionEncryptionKeyRef == "" {
		t.Fatalf("revision did not retain SecretStore references: %#v", repository.revision)
	}
}

func TestPairingRetryNeverOverwritesOrDeletesAdoptedIdentitySecrets(t *testing.T) {
	repository := &pairingRepositoryFake{applyManifestAmbiguousOnce: true, failCredentialsSavedOnce: true}
	secrets := &secretStoreFake{values: map[string][]byte{}}
	remote := &onboardingRemoteFake{
		claimed: ClaimedExchange{ExchangeID: "11111111-1111-1111-1111-111111111111", ExchangeToken: "exchange-token-value", Version: 1, ExpiresAt: time.Now().Add(time.Hour)},
		view:    Exchange{ExchangeID: "11111111-1111-1111-1111-111111111111", ApplicationID: "22222222-2222-2222-2222-222222222222", Version: 1, ExpiresAt: time.Now().Add(time.Hour)},
		delivery: CredentialDelivery{Manifest: ManifestV1{
			SchemaVersion: 1, Issuer: "https://auth.example.com/issuer/shared", TenantID: "33333333-3333-3333-3333-333333333333",
			ApplicationID: "22222222-2222-2222-2222-222222222222", Audience: "urn:easyai:resource:22222222-2222-2222-2222-222222222222",
			Capabilities: []string{"oidc_login", "api_access", "machine_to_machine", "token_introspection", "session_revocation"}, Scopes: []string{"openid", "gateway.access"},
			Clients:        ManifestClients{BrowserLogin: &ManifestClient{ClientID: "browser"}, MachineToMachine: &ManifestClient{ClientID: "service"}},
			SecurityEvents: &ManifestSecurityEvents{TransmitterIssuer: "https://auth.example.com/ssf", ConfigurationEndpoint: "https://auth.example.com/.well-known/ssf-configuration/ssf", Audience: "urn:easyai:ssf:receiver:22222222-2222-2222-2222-222222222222"},
		}, MachineCredential: &MachineCredential{ClientID: "service", ClientSecret: "placeholder", IssuedAt: time.Now()}},
	}
	preparer := &securityEventPreparerFake{}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) { return remote, nil }, preparer)
	pairing, err := service.Start(context.Background(), PairingInput{
		AuthCenterURL: "https://auth.example.com", OnboardingCode: "one-time-code-value",
		PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", LocalTenantKey: "default",
	}, "trace-retry")
	if err != nil {
		t.Fatal(err)
	}

	if _, err := service.Continue(context.Background(), pairing.ID); err == nil {
		t.Fatal("simulated pairing status persistence failure was ignored")
	}
	firstMachine := repository.revision.MachineCredentialRef
	firstSession := repository.revision.SessionEncryptionKeyRef
	if firstMachine == "" || firstSession == "" {
		t.Fatalf("first delivery was not adopted: %#v", repository.revision)
	}
	if _, ok := secrets.values[firstMachine]; !ok {
		t.Fatal("first adopted machine Secret was deleted")
	}
	if _, ok := secrets.values[firstSession]; !ok {
		t.Fatal("first adopted session Secret was deleted")
	}
	for _, reference := range []string{firstMachine, firstSession} {
		if _, queued := repository.pendingSecretCleanups[reference]; queued {
			t.Fatalf("ambiguously adopted identity Secret %q was re-queued", reference)
		}
	}

	if _, err := service.Continue(context.Background(), pairing.ID); err == nil {
		t.Fatal("simulated pairing status persistence failure was ignored")
	}
	secondMachine := repository.revision.MachineCredentialRef
	secondSession := repository.revision.SessionEncryptionKeyRef
	if secondMachine == firstMachine || secondSession == firstSession {
		t.Fatalf("identity Secret references were overwritten in place: first=(%s,%s) second=(%s,%s)", firstMachine, firstSession, secondMachine, secondSession)
	}
	for _, reference := range []string{firstMachine, firstSession} {
		if _, queued := repository.pendingSecretCleanups[reference]; !queued {
			t.Fatalf("first superseded identity Secret %q was not queued for cleanup", reference)
		}
	}

	completed, err := service.Continue(context.Background(), pairing.ID)
	if err != nil {
		t.Fatal(err)
	}
	if completed.Status != PairingCompleted {
		t.Fatalf("retry did not complete: %#v", completed)
	}
	thirdMachine := repository.revision.MachineCredentialRef
	thirdSession := repository.revision.SessionEncryptionKeyRef
	if thirdMachine == secondMachine || thirdSession == secondSession {
		t.Fatalf("second retry overwrote identity Secret references in place: second=(%s,%s) third=(%s,%s)", secondMachine, secondSession, thirdMachine, thirdSession)
	}
	for _, reference := range []string{thirdMachine, thirdSession} {
		if _, ok := secrets.values[reference]; !ok {
			t.Fatalf("active identity Secret %q was deleted", reference)
		}
		if _, queued := repository.pendingSecretCleanups[reference]; queued {
			t.Fatalf("active identity Secret %q remained in cleanup queue", reference)
		}
	}
	for _, reference := range []string{firstMachine, firstSession, secondMachine, secondSession} {
		if _, queued := repository.pendingSecretCleanups[reference]; !queued {
			t.Fatalf("superseded identity Secret %q was not queued for cleanup", reference)
		}
	}
}

func TestPairingExpirationIsPersistedAndExchangeTokenIsDestroyed(t *testing.T) {
	repository := &pairingRepositoryFake{exchange: PairingExchange{
		ID: "pairing", RevisionID: "revision", ExchangeTokenRef: "identity-exchange-pairing",
		Status: PairingPreparing, RemoteVersion: 2, ExpiresAt: time.Now().Add(-time.Minute), Version: 4,
	}}
	secrets := &secretStoreFake{values: map[string][]byte{"identity-exchange-pairing": []byte("temporary-token")}}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) {
		t.Fatal("expired exchange must not call the remote service")
		return nil, nil
	}, nil)

	expired, err := service.Continue(context.Background(), "pairing")
	if err != nil {
		t.Fatal(err)
	}
	if expired.Status != PairingExpired || expired.LastErrorCategory != "exchange_expired" {
		t.Fatalf("expired pairing was not persisted: %#v", expired)
	}
	if _, exists := secrets.values["identity-exchange-pairing"]; exists {
		t.Fatal("expired exchange token remained in SecretStore")
	}
}

func TestPairingPersistsOnlySafeSecurityEventFailureCategory(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{
			ID: "revision", State: RevisionDraft, AuthCenterURL: "https://auth.example.com",
			SessionRevocation: true, MachineCredentialRef: "identity-machine-revision",
		},
		exchange: PairingExchange{
			ID: "pairing", RevisionID: "revision", RemoteExchangeID: "11111111-1111-1111-1111-111111111111",
			ExchangeTokenRef: "identity-exchange-pairing", Status: PairingCredentialsSaved,
			RemoteVersion: 4, ExpiresAt: time.Now().Add(time.Hour), Version: 7,
		},
	}
	secrets := &secretStoreFake{values: map[string][]byte{
		"identity-exchange-pairing": []byte("temporary-exchange-token-value-000000"),
		"identity-machine-revision": []byte("temporary-machine-secret-value-0000000"),
	}}
	remote := &onboardingRemoteFake{}
	preparer := &securityEventPreparerFake{err: safeCategoryTestError{
		category: "discovery_failed",
		message:  "sensitive-marker https://internal.example/ssf token-value",
	}}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) { return remote, nil }, preparer)

	failed, err := service.Continue(context.Background(), "pairing")
	if err == nil {
		t.Fatal("security event discovery failure was ignored")
	}
	if failed.Status != PairingCredentialsSaved || failed.LastErrorCategory != "security_event_discovery_failed" {
		t.Fatalf("unsafe or missing pairing failure state: %#v", failed)
	}
	encoded, marshalErr := json.Marshal(failed)
	if marshalErr != nil {
		t.Fatal(marshalErr)
	}
	if strings.Contains(string(encoded), "sensitive-marker") || strings.Contains(string(encoded), "internal.example") || strings.Contains(string(encoded), "token-value") {
		t.Fatalf("raw security event error escaped through pairing JSON: %s", encoded)
	}

	preparer.err = nil
	completed, err := service.Continue(context.Background(), "pairing")
	if err != nil {
		t.Fatal(err)
	}
	if completed.Status != PairingCompleted || completed.LastErrorCategory != "" {
		t.Fatalf("successful retry did not clear the diagnostic category: %#v", completed)
	}
}

func TestPairingCancellationPersistsIntentBeforeCleaningTemporaryResources(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{
			ID: "revision", State: RevisionDraft, Version: 3, SessionRevocation: true,
			MachineCredentialRef: "identity-machine-revision", SessionEncryptionKeyRef: "identity-session-revision",
		},
		exchange: PairingExchange{
			ID: "pairing", RevisionID: "revision", ExchangeTokenRef: "identity-exchange-pairing",
			Status: PairingCredentialsSaved, CleanupStatus: PairingCleanupNone, RemoteVersion: 4,
			ExpiresAt: time.Now().Add(time.Hour), Version: 7,
		},
	}
	secrets := &secretStoreFake{values: map[string][]byte{
		"identity-exchange-pairing": []byte("temporary-exchange-token-value-000000"),
		"identity-machine-revision": []byte("temporary-machine-secret-value-0000000"),
		"identity-session-revision": []byte("temporary-session-secret-value-0000000"),
	}}
	preparer := &securityEventPreparerFake{}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) {
		t.Fatal("cancellation must not call the onboarding remote")
		return nil, nil
	}, preparer)

	cancelled, err := service.Cancel(context.Background(), "pairing", 7, "trace-cancel", "audit-cancel")
	if err != nil {
		t.Fatal(err)
	}
	if cancelled.Status != PairingCancelled || cancelled.CleanupStatus != PairingCleanupPending || cancelled.CancelledAt == nil {
		t.Fatalf("cancellation intent was not persisted: %#v", cancelled)
	}
	if repository.revision.State != RevisionFailed || repository.revision.LastErrorCategory != "pairing_cancelled" {
		t.Fatalf("draft was not atomically retired: %#v", repository.revision)
	}
	if len(secrets.values) != 3 {
		t.Fatal("synchronous cancellation removed resources before the cleanup worker could resume them")
	}
	replayed, err := service.Cancel(context.Background(), "pairing", 7, "trace-replay", "audit-replay")
	if err != nil || replayed.Version != cancelled.Version || replayed.Status != PairingCancelled {
		t.Fatalf("lost cancellation response could not be replayed safely: %#v err=%v", replayed, err)
	}

	cleaned, err := service.Cleanup(context.Background(), "pairing")
	if err != nil {
		t.Fatal(err)
	}
	if cleaned.CleanupStatus != PairingCleanupCompleted || cleaned.CleanupCompletedAt == nil || !preparer.cleanupCalled {
		t.Fatalf("pairing cleanup was not completed: %#v", cleaned)
	}
	if len(secrets.values) != 0 || repository.revision.MachineCredentialRef != "" || repository.revision.SessionEncryptionKeyRef != "" {
		t.Fatalf("temporary secret references survived cleanup: secrets=%d revision=%#v", len(secrets.values), repository.revision)
	}
}

func TestPairingCleanupRetriesWithSafeCategory(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{ID: "revision", State: RevisionFailed, Version: 4, SessionRevocation: true},
		exchange: PairingExchange{
			ID: "pairing", RevisionID: "revision", Status: PairingCancelled, CleanupStatus: PairingCleanupPending,
			RemoteVersion: 4, ExpiresAt: time.Now().Add(time.Hour), Version: 8,
		},
	}
	preparer := &securityEventPreparerFake{cleanupErr: safeCategoryTestError{
		category: "retirement_pending", message: "sensitive cleanup marker token-value",
	}}
	service := NewPairingService(repository, &secretStoreFake{values: map[string][]byte{}}, nil, preparer)

	pending, err := service.Cleanup(context.Background(), "pairing")
	if err == nil {
		t.Fatal("pending security event retirement was treated as completed")
	}
	if pending.CleanupStatus != PairingCleanupPending || pending.LastErrorCategory != "cleanup_security_event_retirement_pending" || strings.Contains(pending.LastErrorCategory, "sensitive") {
		t.Fatalf("cleanup failure was not safely persisted: %#v", pending)
	}

	preparer.cleanupErr = nil
	completed, err := service.Cleanup(context.Background(), "pairing")
	if err != nil {
		t.Fatal(err)
	}
	if completed.CleanupStatus != PairingCleanupCompleted {
		t.Fatalf("cleanup retry did not complete: %#v", completed)
	}
}

func TestPairingMapsUntrustedRemoteErrorCategoryToStableValue(t *testing.T) {
	repository := &pairingRepositoryFake{exchange: PairingExchange{ID: "pairing", Version: 2}}
	service := NewPairingService(repository, &secretStoreFake{}, nil, nil)

	updated, err := service.updateFromRemote(context.Background(), repository.exchange, Exchange{
		Status: ExchangeFailed, Version: 3, LastErrorCategory: "secret_token_abcdef",
	})
	if err != nil {
		t.Fatal(err)
	}
	if updated.Status != PairingFailed || updated.LastErrorCategory != "remote_exchange_failed" {
		t.Fatalf("untrusted remote category was persisted: %#v", updated)
	}
}

func TestPairingConflictRetirementIsScopedToCurrentBlockedPairing(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{ID: "revision", State: RevisionDraft, Version: 3, SessionRevocation: true},
		exchange: PairingExchange{
			ID: "pairing", RevisionID: "revision", Status: PairingCredentialsSaved,
			CleanupStatus: PairingCleanupNone, Version: 7, LastErrorCategory: "security_event_connection_conflict",
		},
	}
	preparer := &securityEventPreparerFake{}
	service := NewPairingService(repository, &secretStoreFake{}, nil, preparer)

	resolved, err := service.RetireConflictingSecurityEvents(context.Background(), "pairing", 7)
	if err != nil {
		t.Fatal(err)
	}
	if resolved.ID != "pairing" || resolved.LastErrorCategory != "security_event_retirement_pending" || !preparer.conflictRetireCalled || preparer.conflictRevisionID != "revision" {
		t.Fatalf("conflict retirement escaped pairing scope: pairing=%#v preparer=%#v", resolved, preparer)
	}

	preparer.conflictRetireCalled = false
	if _, err := service.RetireConflictingSecurityEvents(context.Background(), "pairing", 6); !errors.Is(err, ErrRevisionConflict) {
		t.Fatalf("stale pairing version error=%v", err)
	}
	repository.exchange.LastErrorCategory = ""
	if _, err := service.RetireConflictingSecurityEvents(context.Background(), "pairing", 7); !errors.Is(err, ErrPairingConflictNotResolvable) {
		t.Fatalf("resolved pairing could retire another connection: %v", err)
	}
	if preparer.conflictRetireCalled {
		t.Fatal("stale conflict action reached the security event manager")
	}
}

func TestCompletedPairingRestoresPreparedReceiverAfterRestart(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{
			ID: "revision", State: RevisionValidated, SessionRevocation: true,
			MachineCredentialRef: "identity-machine-revision",
		},
		exchange: PairingExchange{ID: "pairing", RevisionID: "revision", Status: PairingCompleted, Version: 8},
	}
	secrets := &secretStoreFake{values: map[string][]byte{
		"identity-machine-revision": []byte("machine-secret-value-restored-from-store"),
	}}
	preparer := &securityEventPreparerFake{}
	service := NewPairingService(repository, secrets, nil, preparer)

	if err := service.RestoreCompletedSecurityEvents(context.Background(), "pairing"); err != nil {
		t.Fatal(err)
	}
	if !preparer.called {
		t.Fatal("completed pairing did not reconstruct its prepared security event receiver")
	}
}

func TestCompletedPairingDoesNotRestoreReceiverForSupersededRevision(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{
			ID: "revision", State: RevisionSuperseded, Version: 5, SessionRevocation: true,
			MachineCredentialRef: "identity-machine-revision",
		},
		exchange: PairingExchange{ID: "pairing", RevisionID: "revision", Status: PairingCompleted, Version: 8},
	}
	preparer := &securityEventPreparerFake{}
	service := NewPairingService(repository, &secretStoreFake{values: map[string][]byte{
		"identity-machine-revision": []byte("must-not-be-read"),
	}}, nil, preparer)

	if err := service.RestoreCompletedSecurityEvents(context.Background(), "pairing"); !errors.Is(err, ErrRevisionConflict) {
		t.Fatalf("restore error=%v, want revision conflict", err)
	}
	if preparer.called {
		t.Fatal("superseded Revision reconstructed an orphan prepared Receiver")
	}
}

func TestCancelledPairingCleanupWaitsForCompletedReceiverRestore(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{
			ID: "revision", State: RevisionValidated, Version: 3, SessionRevocation: true,
			MachineCredentialRef: "identity-machine-revision",
		},
		exchange: PairingExchange{
			ID: "pairing", RevisionID: "revision", ExchangeTokenRef: "identity-exchange-pairing",
			Status: PairingCompleted, CleanupStatus: PairingCleanupNone, Version: 8,
		},
	}
	secrets := &secretStoreFake{values: map[string][]byte{
		"identity-exchange-pairing": []byte("exchange-token"),
		"identity-machine-revision": []byte("machine-secret"),
	}}
	preparer := &blockingSecurityEventPreparer{
		prepareStarted: make(chan struct{}),
		releasePrepare: make(chan struct{}),
		cleanupStarted: make(chan struct{}),
	}
	service := NewPairingService(repository, secrets, nil, preparer)
	restoreResult := make(chan error, 1)
	go func() { restoreResult <- service.RestoreCompletedSecurityEvents(context.Background(), "pairing") }()
	<-preparer.prepareStarted

	cancelled, err := service.Cancel(context.Background(), "pairing", 8, "trace", "audit")
	if err != nil {
		t.Fatal(err)
	}
	cleanupResult := make(chan PairingExchange, 1)
	cleanupError := make(chan error, 1)
	go func() {
		cleaned, cleanupErr := service.Cleanup(context.Background(), "pairing")
		cleanupResult <- cleaned
		cleanupError <- cleanupErr
	}()

	select {
	case <-preparer.cleanupStarted:
		t.Fatal("cleanup overtook an in-flight completed receiver restore")
	case <-time.After(100 * time.Millisecond):
	}
	close(preparer.releasePrepare)
	if err := <-restoreResult; !errors.Is(err, ErrRevisionConflict) {
		t.Fatalf("restore error = %v, want revision conflict after cancellation", err)
	}
	if err := <-cleanupError; err != nil {
		t.Fatal(err)
	}
	cleaned := <-cleanupResult
	if cancelled.Status != PairingCancelled || cleaned.CleanupStatus != PairingCleanupCompleted {
		t.Fatalf("cancel/cleanup did not converge: cancelled=%#v cleaned=%#v", cancelled, cleaned)
	}
	if len(secrets.values) != 0 {
		t.Fatalf("cancelled restore left temporary secrets: %d", len(secrets.values))
	}
}

func TestPairingClassifiesExchangeCompletionFailureSeparatelyFromSSF(t *testing.T) {
	repository := &pairingRepositoryFake{
		revision: Revision{ID: "revision", State: RevisionDraft, AuthCenterURL: "https://auth.example.com"},
		exchange: PairingExchange{
			ID: "pairing", RevisionID: "revision", RemoteExchangeID: "11111111-1111-1111-1111-111111111111",
			ExchangeTokenRef: "identity-exchange-pairing", Status: PairingCredentialsSaved,
			RemoteVersion: 4, ExpiresAt: time.Now().Add(time.Hour), Version: 7,
		},
	}
	secrets := &secretStoreFake{values: map[string][]byte{
		"identity-exchange-pairing": []byte("temporary-exchange-token-value-000000"),
	}}
	remote := &onboardingRemoteFake{completeErr: errors.New("upstream body contains sensitive marker")}
	service := NewPairingService(repository, secrets, func(string) (OnboardingRemote, error) { return remote, nil }, nil)

	failed, err := service.Continue(context.Background(), "pairing")
	if err == nil {
		t.Fatal("exchange completion failure was ignored")
	}
	if failed.LastErrorCategory != "exchange_completion_failed" {
		t.Fatalf("completion failure was misclassified: %#v", failed)
	}
}
