package identity

import (
	"context"
	"crypto/rand"
	"errors"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"
)

type PairingStatus string

const (
	PairingMetadataPending  PairingStatus = "metadata_pending"
	PairingPreparing        PairingStatus = "preparing"
	PairingReady            PairingStatus = "ready"
	PairingCredentialsSaved PairingStatus = "credentials_saved"
	PairingCompleted        PairingStatus = "completed"
	PairingFailed           PairingStatus = "failed"
	PairingExpired          PairingStatus = "expired"
	PairingCancelled        PairingStatus = "cancelled"
)

type PairingCleanupStatus string

const (
	PairingCleanupNone      PairingCleanupStatus = "none"
	PairingCleanupPending   PairingCleanupStatus = "pending"
	PairingCleanupCompleted PairingCleanupStatus = "completed"
)

var (
	ErrPairingInProgress            = errors.New("an identity pairing is already in progress")
	ErrPairingNotCancellable        = errors.New("identity pairing cannot be cancelled")
	ErrPairingConflictNotResolvable = errors.New("identity pairing has no resolvable security event conflict")
)

const (
	identityPairingStartReservationTTL = 2 * time.Minute
	identityPairingStartReleaseTimeout = 5 * time.Second
)

type PairingExchange struct {
	ID                 string               `json:"id"`
	RevisionID         string               `json:"revisionId"`
	RemoteExchangeID   string               `json:"remoteExchangeId"`
	ExchangeTokenRef   string               `json:"-"`
	Status             PairingStatus        `json:"status"`
	CleanupStatus      PairingCleanupStatus `json:"cleanupStatus"`
	RemoteVersion      int64                `json:"remoteVersion"`
	ExpiresAt          time.Time            `json:"expiresAt"`
	Version            int64                `json:"version"`
	LastErrorCategory  string               `json:"lastErrorCategory,omitempty"`
	AuthCenterAuditID  string               `json:"authCenterAuditId,omitempty"`
	LastTraceID        string               `json:"lastTraceId,omitempty"`
	CancelledAt        *time.Time           `json:"cancelledAt,omitempty"`
	CleanupCompletedAt *time.Time           `json:"cleanupCompletedAt,omitempty"`
	CreatedAt          time.Time            `json:"createdAt"`
	UpdatedAt          time.Time            `json:"updatedAt"`
}

type PairingExchangeUpdate struct {
	Status            PairingStatus
	RemoteVersion     int64
	LastErrorCategory string
	AuthCenterAuditID string
}

type PairingRepository interface {
	ReserveIdentityPairingStart(context.Context, string, time.Time) error
	ReleaseIdentityPairingStart(context.Context, string) error
	CommitIdentityPairingStart(context.Context, Revision, PairingExchange) (PairingExchange, error)
	IdentityPairingStartBlocked(context.Context) (bool, error)
	IdentityConfigurationRevision(context.Context, string) (Revision, error)
	ApplyIdentityManifest(context.Context, string, int64, ManifestApplication) (Revision, error)
	IdentityPairingExchange(context.Context, string) (PairingExchange, error)
	UpdateIdentityPairingExchange(context.Context, string, int64, PairingExchangeUpdate) (PairingExchange, error)
	RecordIdentityPairingRetryFailure(context.Context, string, PairingStatus, string) (PairingExchange, error)
	CancelIdentityPairingExchange(context.Context, string, int64, string, string) (PairingExchange, error)
	CompleteIdentityPairingCleanup(context.Context, string, int64) (PairingExchange, error)
	RecordIdentityPairingCleanupFailure(context.Context, string, string) (PairingExchange, error)
	QueueIdentitySecretCleanup(context.Context, string, time.Time) error
	RemovePendingIdentitySecretCleanup(context.Context, string) (bool, error)
}

type PairingSecretStore interface {
	Put(context.Context, string, []byte) error
	Get(context.Context, string) ([]byte, error)
	Delete(context.Context, string) error
}

type OnboardingRemote interface {
	Claim(context.Context, string) (ClaimedExchange, error)
	SubmitMetadata(context.Context, ClaimedExchange, ConsumerMetadata, string) (Exchange, error)
	Get(context.Context, string, string) (Exchange, error)
	DeliverCredential(context.Context, Exchange, string, string) (CredentialDelivery, error)
	Complete(context.Context, string, string, string, int64) error
}

type SecurityEventPreparer interface {
	PrepareSecurityEvents(context.Context, Revision, []byte) error
	CleanupPreparedSecurityEvents(context.Context, Revision) error
}

type SecurityEventConflictResolver interface {
	RetireConflictingSecurityEvents(context.Context, Revision) error
}

type PairingService struct {
	repository PairingRepository
	secrets    PairingSecretStore
	remote     func(string) (OnboardingRemote, error)
	security   SecurityEventPreparer
	appEnv     string
	operations sync.Map
}

func NewPairingService(repository PairingRepository, secrets PairingSecretStore, remote func(string) (OnboardingRemote, error), security SecurityEventPreparer, appEnvs ...string) *PairingService {
	appEnv := "production"
	if len(appEnvs) > 0 {
		appEnv = appEnvs[0]
	}
	return &PairingService{repository: repository, secrets: secrets, remote: remote, security: security, appEnv: appEnv}
}

func (service *PairingService) Start(ctx context.Context, input PairingInput, traceID string) (PairingExchange, error) {
	draft, err := NewDraft(input, service.appEnv)
	if err != nil {
		return PairingExchange{}, err
	}
	draft.LastTraceID = strings.TrimSpace(traceID)
	pairingID := uuid.NewString()
	tokenReference := "identity-exchange-" + pairingID
	if err := service.repository.ReserveIdentityPairingStart(ctx, pairingID, time.Now().UTC().Add(identityPairingStartReservationTTL)); err != nil {
		return PairingExchange{}, err
	}
	committed := false
	defer func() {
		if !committed {
			releaseContext, cancel := context.WithTimeout(context.Background(), identityPairingStartReleaseTimeout)
			defer cancel()
			_ = service.repository.ReleaseIdentityPairingStart(releaseContext, pairingID)
		}
	}()
	remote, err := service.remote(draft.AuthCenterURL)
	if err != nil {
		return PairingExchange{}, err
	}
	claimed, err := remote.Claim(ctx, strings.TrimSpace(input.OnboardingCode))
	if err != nil {
		return PairingExchange{}, err
	}
	token := []byte(claimed.ExchangeToken)
	claimed.ExchangeToken = ""
	if err := service.stageIdentitySecret(ctx, tokenReference, token); err != nil {
		clear(token)
		return PairingExchange{}, err
	}
	clear(token)
	pairing := PairingExchange{
		ID: pairingID, RevisionID: draft.ID, RemoteExchangeID: claimed.ExchangeID, ExchangeTokenRef: tokenReference,
		Status: PairingMetadataPending, CleanupStatus: PairingCleanupNone, RemoteVersion: claimed.Version, ExpiresAt: claimed.ExpiresAt,
		Version: 1, LastTraceID: strings.TrimSpace(traceID),
	}
	pairing, err = service.repository.CommitIdentityPairingStart(ctx, draft, pairing)
	if err != nil {
		// Commit may have succeeded even when its response was lost. The
		// staging row is the source of truth: rollback leaves it queued, while
		// a successful commit atomically adopts it. Re-queueing here could
		// delete a Secret that the new Pairing already references.
		return PairingExchange{}, err
	}
	committed = true
	return pairing, nil
}

func (service *PairingService) Cancel(ctx context.Context, pairingID string, expectedVersion int64, traceID, auditID string) (PairingExchange, error) {
	return service.repository.CancelIdentityPairingExchange(ctx, pairingID, expectedVersion, strings.TrimSpace(traceID), strings.TrimSpace(auditID))
}

func (service *PairingService) RetireConflictingSecurityEvents(ctx context.Context, pairingID string, expectedVersion int64) (PairingExchange, error) {
	unlock := service.lockPairingOperation(pairingID)
	defer unlock()
	pairing, err := service.repository.IdentityPairingExchange(ctx, pairingID)
	if err != nil {
		return PairingExchange{}, err
	}
	if pairing.Version != expectedVersion {
		return pairing, ErrRevisionConflict
	}
	if pairing.Status != PairingCredentialsSaved || pairing.CleanupStatus != PairingCleanupNone ||
		pairing.LastErrorCategory != "security_event_connection_conflict" {
		return pairing, ErrPairingConflictNotResolvable
	}
	revision, err := service.repository.IdentityConfigurationRevision(ctx, pairing.RevisionID)
	if err != nil {
		return pairing, err
	}
	if revision.State != RevisionDraft || !revision.SessionRevocation {
		return pairing, ErrPairingConflictNotResolvable
	}
	resolver, ok := service.security.(SecurityEventConflictResolver)
	if !ok {
		return pairing, errors.New("security event conflict recovery is unavailable")
	}
	if err := resolver.RetireConflictingSecurityEvents(ctx, revision); err != nil {
		return pairing, err
	}
	return service.repository.RecordIdentityPairingRetryFailure(ctx, pairing.ID, pairing.Status, "security_event_retirement_pending")
}

// RestoreCompletedSecurityEvents reconstructs the prepared Receiver after a
// process restart while a completed Revision is still awaiting validation or
// activation. The machine Secret is read only from SecretStore and cleared
// immediately after use.
func (service *PairingService) RestoreCompletedSecurityEvents(ctx context.Context, pairingID string) error {
	unlock := service.lockPairingOperation(pairingID)
	defer unlock()
	pairing, err := service.repository.IdentityPairingExchange(ctx, pairingID)
	if err != nil {
		return err
	}
	if pairing.Status != PairingCompleted {
		return ErrRevisionConflict
	}
	revision, err := service.repository.IdentityConfigurationRevision(ctx, pairing.RevisionID)
	if err != nil {
		return err
	}
	if revision.State == RevisionActive {
		return nil
	}
	if revision.State != RevisionDraft && revision.State != RevisionValidated {
		return ErrRevisionConflict
	}
	if !revision.SessionRevocation {
		return nil
	}
	if service.security == nil || revision.MachineCredentialRef == "" {
		return errors.New("completed security event configuration is unavailable")
	}
	secret, err := service.secrets.Get(ctx, revision.MachineCredentialRef)
	if err != nil {
		return err
	}
	defer clear(secret)
	if err := service.security.PrepareSecurityEvents(ctx, revision, secret); err != nil {
		return err
	}
	currentRevision, revisionErr := service.repository.IdentityConfigurationRevision(ctx, pairing.RevisionID)
	if revisionErr != nil {
		return revisionErr
	}
	if currentRevision.State == RevisionActive {
		return nil
	}
	currentPairing, pairingErr := service.repository.IdentityPairingExchange(ctx, pairingID)
	if pairingErr != nil {
		return pairingErr
	}
	if currentPairing.Status == PairingCompleted &&
		(currentRevision.State == RevisionDraft || currentRevision.State == RevisionValidated) {
		return nil
	}
	if cleanupErr := service.security.CleanupPreparedSecurityEvents(ctx, currentRevision); cleanupErr != nil {
		return cleanupErr
	}
	return ErrRevisionConflict
}

func (service *PairingService) Cleanup(ctx context.Context, pairingID string) (PairingExchange, error) {
	unlock := service.lockPairingOperation(pairingID)
	defer unlock()
	pairing, err := service.repository.IdentityPairingExchange(ctx, pairingID)
	if err != nil {
		return PairingExchange{}, err
	}
	if pairing.Status != PairingCancelled {
		return pairing, ErrPairingNotCancellable
	}
	if pairing.CleanupStatus == PairingCleanupCompleted {
		return pairing, nil
	}
	if pairing.CleanupStatus != PairingCleanupPending {
		return pairing, ErrRevisionConflict
	}
	revision, err := service.repository.IdentityConfigurationRevision(ctx, pairing.RevisionID)
	if err != nil {
		return service.recordCleanupFailure(ctx, pairing, "cleanup_revision_unavailable", err)
	}
	if revision.SessionRevocation {
		if service.security == nil {
			return service.recordCleanupFailure(ctx, pairing, "cleanup_security_event_unavailable", errors.New("security event cleanup is unavailable"))
		}
		if err := service.security.CleanupPreparedSecurityEvents(ctx, revision); err != nil {
			return service.recordCleanupFailure(ctx, pairing, pairingCleanupFailureCategory(err), err)
		}
	}
	for _, reference := range []string{pairing.ExchangeTokenRef, revision.MachineCredentialRef, revision.SessionEncryptionKeyRef} {
		if reference == "" {
			continue
		}
		if err := service.secrets.Delete(ctx, reference); err != nil {
			return service.recordCleanupFailure(ctx, pairing, "cleanup_secret_store_failed", err)
		}
	}
	completed, err := service.repository.CompleteIdentityPairingCleanup(ctx, pairing.ID, pairing.Version)
	if err != nil {
		return service.recordCleanupFailure(ctx, pairing, "cleanup_finalize_failed", err)
	}
	return completed, nil
}

func (service *PairingService) recordCleanupFailure(ctx context.Context, pairing PairingExchange, category string, cause error) (PairingExchange, error) {
	if errors.Is(cause, context.Canceled) || errors.Is(cause, context.DeadlineExceeded) {
		return pairing, cause
	}
	recorded, err := service.repository.RecordIdentityPairingCleanupFailure(ctx, pairing.ID, category)
	if err != nil {
		return pairing, cause
	}
	return recorded, cause
}

func (service *PairingService) Continue(ctx context.Context, pairingID string) (PairingExchange, error) {
	unlock := service.lockPairingOperation(pairingID)
	defer unlock()
	pairing, err := service.continuePairing(ctx, pairingID)
	if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return pairing, err
	}
	current, currentErr := service.repository.IdentityPairingExchange(ctx, pairingID)
	if currentErr != nil || !isPairingInProgress(current.Status) {
		return pairing, err
	}
	category := pairingRetryFailureCategory(current.Status, err)
	recorded, recordErr := service.repository.RecordIdentityPairingRetryFailure(ctx, current.ID, current.Status, category)
	if recordErr != nil {
		return current, err
	}
	return recorded, err
}

func (service *PairingService) lockPairingOperation(pairingID string) func() {
	value, _ := service.operations.LoadOrStore(pairingID, &sync.Mutex{})
	mutex := value.(*sync.Mutex)
	mutex.Lock()
	return mutex.Unlock
}

func (service *PairingService) continuePairing(ctx context.Context, pairingID string) (PairingExchange, error) {
	for step := 0; step < 6; step++ {
		pairing, err := service.repository.IdentityPairingExchange(ctx, pairingID)
		if err != nil {
			return PairingExchange{}, err
		}
		if pairing.Status == PairingCompleted || pairing.Status == PairingFailed || pairing.Status == PairingExpired || pairing.Status == PairingCancelled {
			return pairing, nil
		}
		if !pairing.ExpiresAt.After(time.Now()) {
			expired, updateErr := service.repository.UpdateIdentityPairingExchange(ctx, pairing.ID, pairing.Version, PairingExchangeUpdate{
				Status: PairingExpired, RemoteVersion: pairing.RemoteVersion, LastErrorCategory: "exchange_expired",
			})
			if updateErr == nil {
				service.deleteRetiredIdentitySecret(ctx, pairing.ExchangeTokenRef)
			}
			return expired, updateErr
		}
		revision, err := service.repository.IdentityConfigurationRevision(ctx, pairing.RevisionID)
		if err != nil {
			return PairingExchange{}, err
		}
		token, err := service.secrets.Get(ctx, pairing.ExchangeTokenRef)
		if err != nil {
			return PairingExchange{}, err
		}
		remote, err := service.remote(revision.AuthCenterURL)
		if err != nil {
			clear(token)
			return PairingExchange{}, err
		}
		switch pairing.Status {
		case PairingMetadataPending:
			metadata, metadataErr := PairingInput{
				AuthCenterURL: revision.AuthCenterURL, PublicBaseURL: revision.PublicBaseURL,
				WebBaseURL: revision.WebBaseURL, LocalTenantKey: revision.LocalTenantKey,
			}.ConsumerMetadata(true, service.appEnv)
			if metadataErr != nil {
				clear(token)
				return PairingExchange{}, metadataErr
			}
			view, submitErr := remote.SubmitMetadata(ctx, ClaimedExchange{
				ExchangeID: pairing.RemoteExchangeID, ExchangeToken: string(token), ExpiresAt: pairing.ExpiresAt, Version: pairing.RemoteVersion,
			}, metadata, "gateway-pairing-metadata-"+pairing.ID)
			clear(token)
			if submitErr != nil {
				return PairingExchange{}, submitErr
			}
			if _, err := service.updateFromRemote(ctx, pairing, view); err != nil {
				return PairingExchange{}, err
			}
		case PairingPreparing:
			view, getErr := remote.Get(ctx, pairing.RemoteExchangeID, string(token))
			clear(token)
			if getErr != nil {
				return PairingExchange{}, getErr
			}
			updated, err := service.updateFromRemote(ctx, pairing, view)
			if err != nil {
				return PairingExchange{}, err
			}
			if updated.Status == PairingPreparing {
				return updated, nil
			}
		case PairingReady:
			delivery, deliveryErr := remote.DeliverCredential(ctx, Exchange{
				ExchangeID: pairing.RemoteExchangeID, ApplicationID: revision.ApplicationID,
				Status: ExchangeReady, Version: pairing.RemoteVersion, ExpiresAt: pairing.ExpiresAt,
			}, string(token), "gateway-pairing-credential-"+uuid.NewString())
			clear(token)
			if deliveryErr != nil {
				return PairingExchange{}, deliveryErr
			}
			updatedRevision, saveErr := service.saveDelivery(ctx, revision, pairing, delivery)
			if saveErr != nil {
				return PairingExchange{}, saveErr
			}
			_ = updatedRevision
			if _, err := service.repository.UpdateIdentityPairingExchange(ctx, pairing.ID, pairing.Version, PairingExchangeUpdate{
				Status: PairingCredentialsSaved, RemoteVersion: delivery.Version,
			}); err != nil {
				return PairingExchange{}, err
			}
		case PairingCredentialsSaved:
			if revision.SessionRevocation {
				if service.security == nil {
					clear(token)
					return PairingExchange{}, errors.New("security event preparation is unavailable")
				}
				secret, secretErr := service.secrets.Get(ctx, revision.MachineCredentialRef)
				if secretErr != nil {
					clear(token)
					return PairingExchange{}, secretErr
				}
				prepareErr := service.security.PrepareSecurityEvents(ctx, revision, secret)
				clear(secret)
				if prepareErr != nil {
					clear(token)
					return PairingExchange{}, prepareErr
				}
			}
			completeErr := remote.Complete(ctx, pairing.RemoteExchangeID, string(token), "gateway-pairing-complete-"+pairing.ID, pairing.RemoteVersion)
			clear(token)
			if completeErr != nil {
				return PairingExchange{}, pairingStepError{category: "exchange_completion_failed", cause: completeErr}
			}
			completed, err := service.repository.UpdateIdentityPairingExchange(ctx, pairing.ID, pairing.Version, PairingExchangeUpdate{
				Status: PairingCompleted, RemoteVersion: pairing.RemoteVersion,
			})
			if err != nil {
				return PairingExchange{}, err
			}
			service.deleteRetiredIdentitySecret(ctx, pairing.ExchangeTokenRef)
			return completed, nil
		default:
			clear(token)
			return PairingExchange{}, ErrRevisionConflict
		}
	}
	return service.repository.IdentityPairingExchange(ctx, pairingID)
}

func isPairingInProgress(status PairingStatus) bool {
	switch status {
	case PairingMetadataPending, PairingPreparing, PairingReady, PairingCredentialsSaved:
		return true
	default:
		return false
	}
}

type safeErrorCategory interface {
	SafeErrorCategory() string
}

type pairingStepError struct {
	category string
	cause    error
}

func (err pairingStepError) Error() string             { return "identity pairing step failed: " + err.category }
func (err pairingStepError) Unwrap() error             { return err.cause }
func (err pairingStepError) SafeErrorCategory() string { return err.category }

func pairingRetryFailureCategory(status PairingStatus, err error) string {
	var categorized safeErrorCategory
	if errors.As(err, &categorized) && categorized.SafeErrorCategory() == "exchange_completion_failed" {
		return "exchange_completion_failed"
	}
	switch status {
	case PairingMetadataPending:
		return "metadata_submission_failed"
	case PairingPreparing:
		return "exchange_status_unavailable"
	case PairingReady:
		return "credential_delivery_failed"
	case PairingCredentialsSaved:
		if errors.As(err, &categorized) {
			switch categorized.SafeErrorCategory() {
			case "configuration_invalid", "connection_conflict", "discovery_failed", "management_token_failed",
				"stream_create_failed", "stream_response_invalid", "receiver_activation_failed", "preparation_failed", "retirement_pending",
				"credential_handoff_unsafe", "connection_binding_missing", "connection_binding_unavailable",
				"connection_binding_invalid", "connection_binding_mismatch":
				return "security_event_" + categorized.SafeErrorCategory()
			}
		}
		return "security_event_preparation_failed"
	default:
		return "pairing_step_failed"
	}
}

func pairingCleanupFailureCategory(err error) string {
	var categorized safeErrorCategory
	if errors.As(err, &categorized) {
		switch categorized.SafeErrorCategory() {
		case "configuration_invalid", "retirement_pending", "connection_conflict", "connection_cleanup_failed", "secret_cleanup_failed",
			"discovery_failed", "management_token_failed", "stream_create_failed", "stream_response_invalid",
			"receiver_activation_failed", "preparation_failed", "credential_handoff_unsafe":
			return "cleanup_security_event_" + categorized.SafeErrorCategory()
		}
	}
	return "cleanup_security_event_failed"
}

func (service *PairingService) saveDelivery(ctx context.Context, revision Revision, pairing PairingExchange, delivery CredentialDelivery) (Revision, error) {
	if err := delivery.Manifest.Validate(service.appEnv); err != nil {
		return Revision{}, err
	}
	machineReference := ""
	if delivery.Manifest.Clients.MachineToMachine != nil {
		if delivery.MachineCredential == nil || delivery.MachineCredential.ClientID != delivery.Manifest.Clients.MachineToMachine.ClientID {
			return Revision{}, errors.New("onboarding machine credential is invalid")
		}
		machineReference = "identity-machine-" + uuid.NewString()
		secret := []byte(delivery.MachineCredential.ClientSecret)
		delivery.MachineCredential.ClientSecret = ""
		if err := service.stageIdentitySecret(ctx, machineReference, secret); err != nil {
			clear(secret)
			return Revision{}, err
		}
		clear(secret)
	}
	sessionReference := ""
	if delivery.Manifest.Clients.BrowserLogin != nil {
		sessionReference = "identity-session-" + uuid.NewString()
		key := make([]byte, 32)
		if _, err := rand.Read(key); err != nil {
			_ = service.retireIdentitySecret(ctx, machineReference)
			return Revision{}, err
		}
		if err := service.stageIdentitySecret(ctx, sessionReference, key); err != nil {
			clear(key)
			_ = service.retireIdentitySecret(ctx, machineReference)
			return Revision{}, err
		}
		clear(key)
	}
	updated, err := service.repository.ApplyIdentityManifest(ctx, revision.ID, revision.Version, ManifestApplication{
		Manifest: delivery.Manifest, MachineCredentialRef: machineReference, SessionEncryptionKeyRef: sessionReference,
		TraceID: pairing.LastTraceID, AuditID: pairing.AuthCenterAuditID, AppEnv: service.appEnv,
	})
	if err != nil {
		// ApplyIdentityManifest adopts the staged references in the same DB
		// transaction as the Revision update. On rollback the staging rows
		// remain; on an ambiguous successful commit they are gone. Do not
		// re-queue here or a valid active credential could be destroyed.
		return Revision{}, err
	}
	return updated, nil
}

func (service *PairingService) stageIdentitySecret(ctx context.Context, reference string, value []byte) error {
	if err := service.repository.QueueIdentitySecretCleanup(ctx, reference, time.Now().UTC().Add(10*time.Minute)); err != nil {
		return err
	}
	if err := service.secrets.Put(ctx, reference, value); err != nil {
		deleteErr := service.secrets.Delete(ctx, reference)
		if deleteErr == nil {
			_, _ = service.repository.RemovePendingIdentitySecretCleanup(ctx, reference)
		}
		return err
	}
	return nil
}

func (service *PairingService) retireIdentitySecret(ctx context.Context, reference string) error {
	if reference == "" {
		return nil
	}
	return service.repository.QueueIdentitySecretCleanup(ctx, reference, time.Now().UTC())
}

func (service *PairingService) deleteRetiredIdentitySecret(ctx context.Context, reference string) {
	if err := service.secrets.Delete(ctx, reference); err == nil {
		_, _ = service.repository.RemovePendingIdentitySecretCleanup(ctx, reference)
	}
}

func (service *PairingService) updateFromRemote(ctx context.Context, pairing PairingExchange, remote Exchange) (PairingExchange, error) {
	status := PairingPreparing
	category := ""
	switch remote.Status {
	case ExchangeReady, ExchangeCredentialDelivered:
		status = PairingReady
	case ExchangeCompleted:
		status = PairingFailed
		category = "remote_state_invalid"
	case ExchangeFailed:
		status = PairingFailed
		category = "remote_exchange_failed"
	case ExchangeExpired:
		status = PairingExpired
		category = "exchange_expired"
	}
	return service.repository.UpdateIdentityPairingExchange(ctx, pairing.ID, pairing.Version, PairingExchangeUpdate{
		Status: status, RemoteVersion: remote.Version, LastErrorCategory: category, AuthCenterAuditID: remote.AuditID,
	})
}
