package connector_oauth

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"
)

func (r *SQLRepository) claimAuthorizationExchange(ctx context.Context, stateDigest, userID, ownerID string, now, leaseUntil time.Time) (*authorizationClaim, error) {
	if r == nil || r.db == nil {
		return nil, ErrDependencyMissing
	}
	var claim authorizationClaim
	err := withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, userID); err != nil {
			return err
		}
		record, err := scanAuthorization(tx.QueryRowContext(ctx, authorizationSelect+`
 WHERE StateDigest = ? LIMIT 1 FOR UPDATE`, stateDigest))
		if errors.Is(err, sql.ErrNoRows) || (err == nil && record.UserID != userID) {
			return ErrAuthorizationDenied
		}
		if err != nil {
			return err
		}
		if err := validateAuthorizationEffectShape(*record); err != nil {
			return err
		}
		switch record.OAuthEffectState {
		case EffectExchangeResultStaged:
			claim.Record = *record
			return nil
		case EffectExchangeDispatched:
			if record.OAuthActionOwnerID == "" || record.OAuthActionLeaseExpiresAt == nil ||
				record.OAuthExchangeDispatchedAt == nil || record.PendingTokenCiphertext != "" {
				return ErrEffectStateInvalid
			}
			return ErrAuthorizationProgress
		case EffectManualRequired:
			return ErrManualActionRequired
		case EffectExchangeClaimed:
			if record.OAuthExchangeDispatchedAt != nil || record.OAuthActionOwnerID == "" ||
				record.OAuthActionLeaseExpiresAt == nil || record.PendingTokenCiphertext != "" {
				return ErrEffectStateInvalid
			}
			if record.OAuthActionLeaseExpiresAt != nil && record.OAuthActionLeaseExpiresAt.After(now) {
				return ErrAuthorizationProgress
			}
		case EffectNone:
			if record.OAuthExchangeDispatchedAt != nil || record.OAuthActionOwnerID != "" ||
				record.OAuthActionLeaseExpiresAt != nil || record.PendingTokenCiphertext != "" {
				return ErrEffectStateInvalid
			}
		default:
			return ErrEffectStateInvalid
		}
		if !record.ExpiresAt.After(now) {
			return ErrAuthorizationExpired
		}
		principal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE UserId = ? AND ConnectorKey = ? LIMIT 1 FOR UPDATE`, userID, record.ConnectorKey))
		if err != nil && !errors.Is(err, sql.ErrNoRows) {
			return err
		}
		generation := int64(0)
		if principal != nil {
			generation = principal.Generation
		}
		if record.ExpectedGeneration != generation+1 {
			return ErrGenerationMismatch
		}
		newEpoch := record.OAuthActionEpoch + 1
		query := `
UPDATE _System_ConnectedAuthorizationRequest
   SET ConsumedAt = COALESCE(ConsumedAt, ?), OAuthEffectState = ?,
       OAuthEffectVersion = OAuthEffectVersion + 1, OAuthActionOwnerId = ?,
       OAuthActionEpoch = ?, OAuthActionLeaseExpiresAt = ?,
       OAuthExchangeDispatchedAt = NULL, PendingTokenCiphertext = NULL,
       LastModifiedDate = ?
	WHERE Id = ? AND UserId = ? AND ExpectedGeneration = ? AND OAuthActionEpoch = ?
   AND OAuthEffectVersion = ? AND OAuthEffectState = ?
   AND OAuthExchangeDispatchedAt IS NULL AND PendingTokenCiphertext IS NULL`
		args := []any{now, EffectExchangeClaimed, ownerID, newEpoch, leaseUntil, now,
			record.ID, userID, record.ExpectedGeneration, record.OAuthActionEpoch,
			record.OAuthEffectVersion, record.OAuthEffectState}
		if record.OAuthEffectState == EffectNone {
			query += ` AND OAuthActionOwnerId IS NULL AND OAuthActionLeaseExpiresAt IS NULL`
		} else {
			query += ` AND BINARY OAuthActionOwnerId = BINARY ?
   AND OAuthActionLeaseExpiresAt = ? AND OAuthActionLeaseExpiresAt <= ?`
			args = append(args, record.OAuthActionOwnerID, record.OAuthActionLeaseExpiresAt, now)
		}
		result, err := tx.ExecContext(ctx, query, args...)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		record.ConsumedAt = &now
		record.OAuthEffectState = EffectExchangeClaimed
		record.OAuthActionOwnerID = ownerID
		record.OAuthActionEpoch = newEpoch
		record.OAuthActionLeaseExpiresAt = &leaseUntil
		claim = authorizationClaim{Record: *record, OwnerID: ownerID, Epoch: newEpoch}
		return nil
	})
	return &claim, err
}

func (r *SQLRepository) markAuthorizationExchangeDispatched(ctx context.Context, claim authorizationClaim, now time.Time) error {
	return r.withAuthorizationFence(ctx, claim, func(tx *sql.Tx) error {
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedAuthorizationRequest
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       OAuthExchangeDispatchedAt = ?, LastModifiedDate = ?
 WHERE Id = ? AND UserId = ? AND ExpectedGeneration = ? AND OAuthActionEpoch = ?
   AND BINARY OAuthActionOwnerId = BINARY ? AND OAuthEffectState = ?`,
			EffectExchangeDispatched, now, now, claim.Record.ID, claim.Record.UserID,
			claim.Record.ExpectedGeneration, claim.Epoch, claim.OwnerID, EffectExchangeClaimed)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) stageAuthorizationResult(ctx context.Context, claim authorizationClaim, ciphertext string, now time.Time) error {
	return r.withAuthorizationFence(ctx, claim, func(tx *sql.Tx) error {
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedAuthorizationRequest
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       PendingTokenCiphertext = ?, OAuthActionOwnerId = NULL,
       OAuthActionLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE Id = ? AND UserId = ? AND ExpectedGeneration = ? AND OAuthActionEpoch = ?
   AND BINARY OAuthActionOwnerId = BINARY ? AND OAuthEffectState = ?
   AND OAuthExchangeDispatchedAt IS NOT NULL`,
			EffectExchangeResultStaged, ciphertext, now, claim.Record.ID, claim.Record.UserID,
			claim.Record.ExpectedGeneration, claim.Epoch, claim.OwnerID, EffectExchangeDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) reconcileAuthorizationStage(ctx context.Context, claim authorizationClaim, ciphertext string, now time.Time) (bool, error) {
	if r == nil || r.db == nil {
		return false, ErrDependencyMissing
	}
	staged := false
	err := withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, claim.Record.UserID); err != nil {
			return err
		}
		record, err := scanAuthorization(tx.QueryRowContext(ctx, authorizationSelect+`
 WHERE Id = ? AND UserId = ? LIMIT 1 FOR UPDATE`, claim.Record.ID, claim.Record.UserID))
		if err != nil {
			return err
		}
		if err := validateAuthorizationEffectShape(*record); err != nil {
			return err
		}
		if record.ExpectedGeneration != claim.Record.ExpectedGeneration || record.OAuthActionEpoch != claim.Epoch {
			return ErrOwnershipLost
		}
		switch record.OAuthEffectState {
		case EffectExchangeResultStaged:
			if record.PendingTokenCiphertext != ciphertext {
				result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedAuthorizationRequest
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       LastModifiedDate = ?
 WHERE Id = ? AND UserId = ? AND ExpectedGeneration = ? AND OAuthActionEpoch = ?
   AND OAuthEffectVersion = ? AND OAuthEffectState = ? AND ConsumedAt = ?
   AND OAuthActionOwnerId IS NULL AND OAuthActionLeaseExpiresAt IS NULL
   AND OAuthExchangeDispatchedAt = ? AND PendingTokenCiphertext = ?`,
					EffectManualRequired, now, record.ID, record.UserID, record.ExpectedGeneration,
					record.OAuthActionEpoch, record.OAuthEffectVersion, EffectExchangeResultStaged,
					record.ConsumedAt, record.OAuthExchangeDispatchedAt, record.PendingTokenCiphertext)
				if err != nil {
					return err
				}
				won, err := exactlyOneRow(result)
				if err != nil {
					return err
				}
				if !won {
					return ErrOwnershipLost
				}
				return nil
			}
			staged = true
			return nil
		case EffectManualRequired:
			return nil
		case EffectExchangeDispatched:
			if record.OAuthActionOwnerID != claim.OwnerID || record.OAuthActionLeaseExpiresAt == nil ||
				record.OAuthExchangeDispatchedAt == nil || record.PendingTokenCiphertext != "" {
				return ErrOwnershipLost
			}
		default:
			return ErrEffectStateInvalid
		}
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedAuthorizationRequest
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       OAuthActionOwnerId = NULL, OAuthActionLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE Id = ? AND UserId = ? AND ExpectedGeneration = ? AND OAuthActionEpoch = ?
   AND BINARY OAuthActionOwnerId = BINARY ? AND OAuthEffectState = ?
   AND OAuthExchangeDispatchedAt IS NOT NULL AND PendingTokenCiphertext IS NULL`,
			EffectManualRequired, now, record.ID, record.UserID, record.ExpectedGeneration,
			record.OAuthActionEpoch, claim.OwnerID, EffectExchangeDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
	return staged, err
}

func (r *SQLRepository) markAuthorizationManual(ctx context.Context, claim authorizationClaim, now time.Time) error {
	return r.withAuthorizationFence(ctx, claim, func(tx *sql.Tx) error {
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedAuthorizationRequest
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       OAuthActionOwnerId = NULL, OAuthActionLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE Id = ? AND UserId = ? AND ExpectedGeneration = ? AND OAuthActionEpoch = ?
   AND BINARY OAuthActionOwnerId = BINARY ? AND OAuthEffectState = ?`,
			EffectManualRequired, now, claim.Record.ID, claim.Record.UserID,
			claim.Record.ExpectedGeneration, claim.Epoch, claim.OwnerID, EffectExchangeDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) markStagedAuthorizationManual(ctx context.Context, record authorizationRecord, now time.Time) error {
	if r == nil || r.db == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, record.UserID); err != nil {
			return err
		}
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedAuthorizationRequest
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1, LastModifiedDate = ?
 WHERE Id = ? AND UserId = ? AND ConnectorKey = ? AND ExpectedGeneration = ?
   AND OAuthEffectState = ? AND OAuthEffectVersion = ? AND PendingTokenCiphertext = ?`,
			EffectManualRequired, now, record.ID, record.UserID, record.ConnectorKey,
			record.ExpectedGeneration, EffectExchangeResultStaged, record.OAuthEffectVersion,
			record.PendingTokenCiphertext)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) withAuthorizationFence(ctx context.Context, claim authorizationClaim, fn func(*sql.Tx) error) error {
	if r == nil || r.db == nil || fn == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, claim.Record.UserID); err != nil {
			return err
		}
		record, err := scanAuthorization(tx.QueryRowContext(ctx, authorizationSelect+`
 WHERE Id = ? AND UserId = ? LIMIT 1 FOR UPDATE`, claim.Record.ID, claim.Record.UserID))
		if err != nil {
			return err
		}
		if err := validateAuthorizationEffectShape(*record); err != nil {
			return err
		}
		if record.ExpectedGeneration != claim.Record.ExpectedGeneration ||
			record.OAuthActionEpoch != claim.Epoch || record.OAuthActionOwnerID != claim.OwnerID {
			return ErrOwnershipLost
		}
		return fn(tx)
	})
}

func (r *SQLRepository) stageRefreshResult(ctx context.Context, claim refreshClaim, ciphertext string, now time.Time) error {
	return r.withRefreshFence(ctx, claim, func(tx *sql.Tx) error {
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       PendingTokenCiphertext = ?, RefreshOwnerId = NULL,
       RefreshLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND RefreshOwnershipEpoch = ? AND BINARY RefreshOwnerId = BINARY ?
   AND OAuthEffectState = ? AND RefreshDispatchStartedAt IS NOT NULL`,
			EffectRefreshResultStaged, ciphertext, now, claim.Principal.ID, claim.Principal.Generation,
			claim.Token.TokenVersion, claim.Epoch, claim.OwnerID, EffectRefreshDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) reconcileRefreshStage(ctx context.Context, claim refreshClaim, ciphertext string, now time.Time) (bool, error) {
	if r == nil || r.db == nil {
		return false, ErrDependencyMissing
	}
	staged := false
	err := withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, claim.Principal.UserID); err != nil {
			return err
		}
		principal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE Id = ? AND UserId = ? LIMIT 1 FOR UPDATE`, claim.Principal.ID, claim.Principal.UserID))
		if err != nil {
			return err
		}
		token, err := scanToken(tx.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1 FOR UPDATE`, claim.Principal.ID))
		if err != nil {
			return err
		}
		if err := validateTokenEffectShape(*token); err != nil {
			return err
		}
		if principal.Generation != claim.Principal.Generation || token.PrincipalGeneration != claim.Principal.Generation ||
			token.TokenVersion != claim.Token.TokenVersion || token.RefreshOwnershipEpoch != claim.Epoch {
			return ErrOwnershipLost
		}
		switch token.OAuthEffectState {
		case EffectRefreshResultStaged:
			if token.PendingTokenCiphertext != ciphertext {
				if principal.State != PrincipalConnected {
					return ErrOwnershipLost
				}
				if err := setPrincipalReconnectRequired(ctx, tx, claim.Principal, now); err != nil {
					return err
				}
				result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND RefreshOwnershipEpoch = ? AND OAuthEffectVersion = ?
   AND OAuthEffectState = ? AND PendingTokenCiphertext = ?
   AND RefreshOwnerId IS NULL AND RefreshLeaseExpiresAt IS NULL
   AND RefreshDispatchStartedAt = ?`, EffectManualRequired, now, claim.Principal.ID,
					claim.Principal.Generation, claim.Token.TokenVersion, claim.Epoch,
					token.OAuthEffectVersion, EffectRefreshResultStaged, token.PendingTokenCiphertext,
					token.RefreshDispatchStartedAt)
				if err != nil {
					return err
				}
				won, err := exactlyOneRow(result)
				if err != nil {
					return err
				}
				if !won {
					return ErrOwnershipLost
				}
				return nil
			}
			staged = true
			return nil
		case EffectManualRequired:
			return nil
		case EffectRefreshDispatched:
			if token.RefreshOwnerID != claim.OwnerID || token.RefreshDispatchStartedAt == nil || principal.State != PrincipalConnected {
				return ErrOwnershipLost
			}
		default:
			return ErrEffectStateInvalid
		}
		if err := setPrincipalReconnectRequired(ctx, tx, claim.Principal, now); err != nil {
			return err
		}
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       RefreshOwnerId = NULL, RefreshLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND RefreshOwnershipEpoch = ? AND BINARY RefreshOwnerId = BINARY ? AND OAuthEffectState = ?`,
			EffectManualRequired, now, claim.Principal.ID, claim.Principal.Generation,
			claim.Token.TokenVersion, claim.Epoch, claim.OwnerID, EffectRefreshDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
	return staged, err
}

func (r *SQLRepository) markRefreshManual(ctx context.Context, claim refreshClaim, now time.Time) error {
	if r == nil || r.db == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, claim.Principal.UserID); err != nil {
			return err
		}
		principal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE Id = ? AND UserId = ? LIMIT 1 FOR UPDATE`, claim.Principal.ID, claim.Principal.UserID))
		if err != nil {
			return err
		}
		token, err := scanToken(tx.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1 FOR UPDATE`, claim.Principal.ID))
		if err != nil {
			return err
		}
		if err := validateTokenEffectShape(*token); err != nil {
			return err
		}
		if principal.Generation != claim.Principal.Generation || token.PrincipalGeneration != claim.Principal.Generation ||
			token.TokenVersion != claim.Token.TokenVersion || token.RefreshOwnershipEpoch != claim.Epoch ||
			token.RefreshOwnerID != claim.OwnerID ||
			(token.OAuthEffectState != EffectRefreshClaimed && token.OAuthEffectState != EffectRefreshDispatched) {
			return ErrOwnershipLost
		}
		if principal.State != PrincipalConnected {
			return ErrOwnershipLost
		}
		if err := setPrincipalReconnectRequired(ctx, tx, claim.Principal, now); err != nil {
			return err
		}
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       RefreshOwnerId = NULL, RefreshLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND RefreshOwnershipEpoch = ? AND BINARY RefreshOwnerId = BINARY ?
   AND OAuthEffectState IN (?, ?)`,
			EffectManualRequired, now, claim.Principal.ID, claim.Principal.Generation,
			claim.Token.TokenVersion, claim.Epoch, claim.OwnerID, EffectRefreshClaimed, EffectRefreshDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) markStagedRefreshManual(ctx context.Context, principal ConnectedPrincipal, staged tokenRecord, now time.Time) error {
	if r == nil || r.db == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, principal.UserID); err != nil {
			return err
		}
		lockedPrincipal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE Id = ? AND UserId = ? LIMIT 1 FOR UPDATE`, principal.ID, principal.UserID))
		if err != nil {
			return err
		}
		if lockedPrincipal.State != PrincipalConnected || lockedPrincipal.Generation != principal.Generation {
			return ErrOwnershipLost
		}
		if err := setPrincipalReconnectRequired(ctx, tx, principal, now); err != nil {
			return err
		}
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       RefreshOwnerId = NULL, RefreshLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND OAuthEffectVersion = ? AND OAuthEffectState = ? AND PendingTokenCiphertext = ?`,
			EffectManualRequired, now, principal.ID, principal.Generation, staged.TokenVersion,
			staged.OAuthEffectVersion, EffectRefreshResultStaged, staged.PendingTokenCiphertext)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) finalizeStagedRefresh(ctx context.Context, principal ConnectedPrincipal, token tokenRecord, material tokenMaterial, now time.Time) error {
	if r == nil || r.db == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, principal.UserID); err != nil {
			return err
		}
		lockedPrincipal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE Id = ? AND UserId = ? LIMIT 1 FOR UPDATE`, principal.ID, principal.UserID))
		if err != nil {
			return err
		}
		if lockedPrincipal.State != PrincipalConnected || lockedPrincipal.Generation != principal.Generation {
			return ErrOwnershipLost
		}
		lockedToken, err := scanToken(tx.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1 FOR UPDATE`, principal.ID))
		if err != nil {
			return err
		}
		if err := validateTokenEffectShape(*lockedToken); err != nil {
			return err
		}
		if lockedToken.PrincipalGeneration != principal.Generation ||
			lockedToken.TokenVersion != token.TokenVersion ||
			lockedToken.RefreshOwnershipEpoch != token.RefreshOwnershipEpoch ||
			lockedToken.OAuthEffectVersion != token.OAuthEffectVersion ||
			lockedToken.OAuthEffectState != EffectRefreshResultStaged ||
			lockedToken.PendingTokenCiphertext != token.PendingTokenCiphertext ||
			lockedToken.RefreshDispatchStartedAt == nil || token.RefreshDispatchStartedAt == nil ||
			!lockedToken.RefreshDispatchStartedAt.Equal(*token.RefreshDispatchStartedAt) {
			return ErrOwnershipLost
		}
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET AccessTokenCiphertext = ?, RefreshTokenCiphertext = ?, TokenType = ?, GrantedScopes = ?,
       ExpiresAt = ?, EncryptionKeyId = ?, TokenVersion = TokenVersion + 1,
       RefreshOwnerId = NULL, RefreshLeaseExpiresAt = NULL, RefreshDispatchStartedAt = NULL,
       OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       PendingTokenCiphertext = NULL, LastModifiedDate = ?
	 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
	   AND RefreshOwnershipEpoch = ? AND OAuthEffectVersion = ?
	   AND OAuthEffectState = ? AND PendingTokenCiphertext = ?
	   AND RefreshOwnerId IS NULL AND RefreshLeaseExpiresAt IS NULL
	   AND RefreshDispatchStartedAt = ?`,
			material.AccessCiphertext, nullableString(material.RefreshCiphertext), material.TokenType,
			material.GrantedScopes, nullableTime(material.ExpiresAt), material.EncryptionKeyID,
			EffectNone, now, principal.ID, principal.Generation, token.TokenVersion,
			token.RefreshOwnershipEpoch, token.OAuthEffectVersion, EffectRefreshResultStaged, token.PendingTokenCiphertext,
			lockedToken.RefreshDispatchStartedAt)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) claimRevoke(ctx context.Context, userID, connectorKey, ownerID string, now, leaseUntil time.Time) (*revokeClaim, error) {
	if r == nil || r.db == nil {
		return nil, ErrDependencyMissing
	}
	var claim *revokeClaim
	err := withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, userID); err != nil {
			return err
		}
		principal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE UserId = ? AND ConnectorKey = ? LIMIT 1 FOR UPDATE`, userID, connectorKey))
		if errors.Is(err, sql.ErrNoRows) {
			return ErrPrincipalNotFound
		}
		if err != nil {
			return err
		}
		if principal.State == PrincipalConnected {
			return ErrReconnectRequired
		}
		token, err := scanToken(tx.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1 FOR UPDATE`, principal.ID))
		if errors.Is(err, sql.ErrNoRows) {
			return nil
		}
		if err != nil {
			return err
		}
		if principal.Generation != token.PrincipalGeneration && principal.Generation != token.PrincipalGeneration+1 {
			return ErrGenerationMismatch
		}
		if err := validateTokenEffectShape(*token); err != nil {
			return err
		}
		switch token.OAuthEffectState {
		case EffectRevokeSucceeded:
			claim = &revokeClaim{Principal: *principal, Token: *token}
			return nil
		case EffectRevokeDispatched:
			if token.RefreshOwnerID == "" || token.RefreshLeaseExpiresAt == nil ||
				token.RefreshDispatchStartedAt == nil || token.PendingTokenCiphertext != "" {
				return ErrEffectStateInvalid
			}
			return ErrRefreshInProgress
		case EffectManualRequired:
			return ErrManualActionRequired
		case EffectRevokeClaimed:
			if token.RefreshDispatchStartedAt != nil || token.RefreshOwnerID == "" ||
				token.RefreshLeaseExpiresAt == nil || token.PendingTokenCiphertext != "" {
				return ErrEffectStateInvalid
			}
			if token.RefreshLeaseExpiresAt != nil && token.RefreshLeaseExpiresAt.After(now) {
				return ErrRefreshInProgress
			}
		case EffectRevokePending:
			if token.RefreshDispatchStartedAt != nil || token.RefreshOwnerID != "" ||
				token.RefreshLeaseExpiresAt != nil || token.PendingTokenCiphertext != "" {
				return ErrEffectStateInvalid
			}
		default:
			return ErrEffectStateInvalid
		}
		newEpoch := token.RefreshOwnershipEpoch + 1
		query := `
UPDATE _System_ConnectedPrincipalToken
   SET RefreshOwnershipEpoch = ?, RefreshOwnerId = ?, RefreshLeaseExpiresAt = ?,
       RefreshDispatchStartedAt = NULL, OAuthEffectState = ?,
       OAuthEffectVersion = OAuthEffectVersion + 1, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND RefreshOwnershipEpoch = ? AND OAuthEffectVersion = ? AND OAuthEffectState = ?
   AND RefreshDispatchStartedAt IS NULL AND PendingTokenCiphertext IS NULL`
		args := []any{newEpoch, ownerID, leaseUntil, EffectRevokeClaimed, now,
			principal.ID, token.PrincipalGeneration, token.TokenVersion,
			token.RefreshOwnershipEpoch, token.OAuthEffectVersion, token.OAuthEffectState}
		if token.OAuthEffectState == EffectRevokePending {
			query += ` AND RefreshOwnerId IS NULL AND RefreshLeaseExpiresAt IS NULL`
		} else {
			query += ` AND BINARY RefreshOwnerId = BINARY ?
   AND RefreshLeaseExpiresAt = ? AND RefreshLeaseExpiresAt <= ?`
			args = append(args, token.RefreshOwnerID, token.RefreshLeaseExpiresAt, now)
		}
		result, err := tx.ExecContext(ctx, query, args...)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		token.RefreshOwnershipEpoch = newEpoch
		token.RefreshOwnerID = ownerID
		token.RefreshLeaseExpiresAt = &leaseUntil
		token.OAuthEffectState = EffectRevokeClaimed
		claim = &revokeClaim{Principal: *principal, Token: *token, OwnerID: ownerID, Epoch: newEpoch}
		return nil
	})
	return claim, err
}

func (r *SQLRepository) markRevokeDispatched(ctx context.Context, claim revokeClaim, now time.Time) error {
	return r.withRevokeFence(ctx, claim, func(tx *sql.Tx) error {
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET RefreshDispatchStartedAt = ?, OAuthEffectState = ?,
       OAuthEffectVersion = OAuthEffectVersion + 1, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND RefreshOwnershipEpoch = ? AND BINARY RefreshOwnerId = BINARY ? AND OAuthEffectState = ?`,
			now, EffectRevokeDispatched, now, claim.Principal.ID, claim.Token.PrincipalGeneration,
			claim.Token.TokenVersion, claim.Epoch, claim.OwnerID, EffectRevokeClaimed)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) markRevokeManual(ctx context.Context, claim revokeClaim, now time.Time) error {
	return r.withRevokeFence(ctx, claim, func(tx *sql.Tx) error {
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       RefreshOwnerId = NULL, RefreshLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
	   AND RefreshOwnershipEpoch = ? AND BINARY RefreshOwnerId = BINARY ?
	   AND OAuthEffectState IN (?, ?)`,
			EffectManualRequired, now, claim.Principal.ID, claim.Token.PrincipalGeneration,
			claim.Token.TokenVersion, claim.Epoch, claim.OwnerID, EffectRevokeClaimed, EffectRevokeDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) markRevokeSucceeded(ctx context.Context, claim revokeClaim, now time.Time) error {
	return r.withRevokeFence(ctx, claim, func(tx *sql.Tx) error {
		result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipalToken
   SET OAuthEffectState = ?, OAuthEffectVersion = OAuthEffectVersion + 1,
       RefreshOwnerId = NULL, RefreshLeaseExpiresAt = NULL, LastModifiedDate = ?
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
   AND RefreshOwnershipEpoch = ? AND BINARY RefreshOwnerId = BINARY ?
   AND OAuthEffectState = ? AND RefreshDispatchStartedAt IS NOT NULL`,
			EffectRevokeSucceeded, now, claim.Principal.ID, claim.Token.PrincipalGeneration,
			claim.Token.TokenVersion, claim.Epoch, claim.OwnerID, EffectRevokeDispatched)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) withRevokeFence(ctx context.Context, claim revokeClaim, fn func(*sql.Tx) error) error {
	if r == nil || r.db == nil || fn == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, claim.Principal.UserID); err != nil {
			return err
		}
		principal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE Id = ? AND UserId = ? LIMIT 1 FOR UPDATE`, claim.Principal.ID, claim.Principal.UserID))
		if err != nil {
			return err
		}
		if principal.State == PrincipalConnected || (principal.Generation != claim.Token.PrincipalGeneration && principal.Generation != claim.Token.PrincipalGeneration+1) {
			return ErrOwnershipLost
		}
		token, err := scanToken(tx.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1 FOR UPDATE`, claim.Principal.ID))
		if err != nil {
			return err
		}
		if err := validateTokenEffectShape(*token); err != nil {
			return err
		}
		if token.PrincipalGeneration != claim.Token.PrincipalGeneration || token.TokenVersion != claim.Token.TokenVersion ||
			token.RefreshOwnershipEpoch != claim.Epoch || token.RefreshOwnerID != claim.OwnerID {
			return ErrOwnershipLost
		}
		return fn(tx)
	})
}

func (r *SQLRepository) deleteRevokedToken(ctx context.Context, userID, connectorKey, principalID string, generation, tokenVersion int64) error {
	if r == nil || r.db == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, userID); err != nil {
			return err
		}
		principal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE Id = ? AND UserId = ? AND ConnectorKey = ? LIMIT 1 FOR UPDATE`, principalID, userID, connectorKey))
		if err != nil {
			return err
		}
		if principal.State == PrincipalConnected || (principal.Generation != generation && principal.Generation != generation+1) {
			return ErrOwnershipLost
		}
		token, err := scanToken(tx.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1 FOR UPDATE`, principalID))
		if err != nil {
			return err
		}
		if token.PrincipalGeneration != generation || token.TokenVersion != tokenVersion ||
			token.OAuthEffectState != EffectRevokeSucceeded {
			return ErrOwnershipLost
		}
		if err := validateTokenEffectShape(*token); err != nil {
			return err
		}
		result, err := tx.ExecContext(ctx, `
DELETE FROM _System_ConnectedPrincipalToken
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
	AND OAuthEffectState = ? AND OAuthEffectVersion = ?
	AND RefreshOwnerId IS NULL AND RefreshLeaseExpiresAt IS NULL
	AND RefreshDispatchStartedAt IS NOT NULL AND PendingTokenCiphertext IS NULL`,
			principalID, generation, tokenVersion, EffectRevokeSucceeded, token.OAuthEffectVersion)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func (r *SQLRepository) loadEffectStatus(ctx context.Context, userID, connectorKey string, now time.Time) (EffectStatus, error) {
	if r == nil || r.db == nil {
		return EffectStatus{}, ErrDependencyMissing
	}
	status := EffectStatus{UserID: userID, ConnectorKey: connectorKey, EffectState: EffectNone}
	authorization, err := scanAuthorization(r.db.QueryRowContext(ctx, authorizationSelect+`
 WHERE UserId = ? AND ConnectorKey = ? LIMIT 1`, userID, connectorKey))
	if err == nil {
		if err := validateAuthorizationEffectShape(*authorization); err != nil {
			return EffectStatus{}, err
		}
		status.EffectState = authorization.OAuthEffectState
		switch authorization.OAuthEffectState {
		case EffectManualRequired:
			if authorization.OAuthActionOwnerID != "" || authorization.OAuthActionLeaseExpiresAt != nil {
				return EffectStatus{}, ErrEffectStateInvalid
			}
			status.CanAttest = true
		case EffectExchangeDispatched:
			if authorization.OAuthActionOwnerID == "" || authorization.OAuthActionLeaseExpiresAt == nil ||
				authorization.OAuthExchangeDispatchedAt == nil || authorization.PendingTokenCiphertext != "" {
				return EffectStatus{}, ErrEffectStateInvalid
			}
			status.CanAttest = !authorization.OAuthActionLeaseExpiresAt.After(now)
		}
		return status, nil
	}
	if !errors.Is(err, sql.ErrNoRows) {
		return EffectStatus{}, err
	}
	principal, err := scanPrincipal(r.db.QueryRowContext(ctx, principalSelect+`
 WHERE UserId = ? AND ConnectorKey = ? LIMIT 1`, userID, connectorKey))
	if errors.Is(err, sql.ErrNoRows) {
		return status, nil
	}
	if err != nil {
		return EffectStatus{}, err
	}
	status.PrincipalState = principal.State
	token, err := scanToken(r.db.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1`, principal.ID))
	if errors.Is(err, sql.ErrNoRows) {
		return status, nil
	}
	if err != nil {
		return EffectStatus{}, err
	}
	if err := validateTokenEffectShape(*token); err != nil {
		return EffectStatus{}, err
	}
	status.EffectState = token.OAuthEffectState
	switch token.OAuthEffectState {
	case EffectManualRequired:
		if token.RefreshOwnerID != "" || token.RefreshLeaseExpiresAt != nil {
			return EffectStatus{}, ErrEffectStateInvalid
		}
		status.CanAttest = true
	case EffectRefreshDispatched, EffectRevokeDispatched:
		if token.RefreshOwnerID == "" || token.RefreshLeaseExpiresAt == nil ||
			token.RefreshDispatchStartedAt == nil || token.PendingTokenCiphertext != "" {
			return EffectStatus{}, ErrEffectStateInvalid
		}
		status.CanAttest = !token.RefreshLeaseExpiresAt.After(now)
	}
	return status, nil
}

func (r *SQLRepository) attestExternalCleanup(ctx context.Context, request CleanupAttestation, now time.Time, audit CleanupAuditEmitter) error {
	if r == nil || r.db == nil || audit == nil {
		return ErrDependencyMissing
	}
	return withPessimisticTx(ctx, r.db, func(tx *sql.Tx) error {
		if err := lockUser(ctx, tx, request.UserID); err != nil {
			return err
		}
		authorization, err := scanAuthorization(tx.QueryRowContext(ctx, authorizationSelect+`
 WHERE UserId = ? AND ConnectorKey = ? LIMIT 1 FOR UPDATE`, request.UserID, request.ConnectorKey))
		if err == nil {
			if err := validateAuthorizationEffectShape(*authorization); err != nil {
				return err
			}
			switch authorization.OAuthEffectState {
			case EffectManualRequired:
				if authorization.OAuthActionOwnerID != "" || authorization.OAuthActionLeaseExpiresAt != nil {
					return ErrEffectStateInvalid
				}
			case EffectExchangeDispatched:
				if authorization.OAuthActionLeaseExpiresAt == nil || authorization.OAuthActionLeaseExpiresAt.After(now) {
					return ErrAuthorizationProgress
				}
			default:
				return ErrManualActionRequired
			}
			event := cleanupAuditEvent(request, authorization.ID, authorization.OAuthEffectState, authorization.OAuthEffectVersion, now)
			if err := audit.EmitOAuthCleanup(ctx, tx, event); err != nil {
				return err
			}
			query := `
DELETE FROM _System_ConnectedAuthorizationRequest
	WHERE Id = ? AND UserId = ? AND ConnectorKey = ? AND OAuthEffectState = ? AND OAuthEffectVersion = ?`
			args := []any{authorization.ID, request.UserID, request.ConnectorKey,
				authorization.OAuthEffectState, authorization.OAuthEffectVersion}
			if authorization.OAuthEffectState == EffectManualRequired {
				query += ` AND OAuthActionOwnerId IS NULL AND OAuthActionLeaseExpiresAt IS NULL`
			} else {
				query += ` AND OAuthActionEpoch = ? AND BINARY OAuthActionOwnerId = BINARY ?
	   AND OAuthActionLeaseExpiresAt = ? AND OAuthActionLeaseExpiresAt <= ?
	   AND OAuthExchangeDispatchedAt = ? AND PendingTokenCiphertext IS NULL`
				args = append(args, authorization.OAuthActionEpoch, authorization.OAuthActionOwnerID,
					authorization.OAuthActionLeaseExpiresAt, now, authorization.OAuthExchangeDispatchedAt)
			}
			result, err := tx.ExecContext(ctx, query, args...)
			if err != nil {
				return err
			}
			won, err := exactlyOneRow(result)
			if err != nil {
				return err
			}
			if !won {
				return ErrOwnershipLost
			}
			return nil
		}
		if !errors.Is(err, sql.ErrNoRows) {
			return err
		}
		principal, err := scanPrincipal(tx.QueryRowContext(ctx, principalSelect+`
 WHERE UserId = ? AND ConnectorKey = ? LIMIT 1 FOR UPDATE`, request.UserID, request.ConnectorKey))
		if errors.Is(err, sql.ErrNoRows) {
			return ErrPrincipalNotFound
		}
		if err != nil {
			return err
		}
		token, err := scanToken(tx.QueryRowContext(ctx, tokenSelect+`
 WHERE ConnectedPrincipalId = ? LIMIT 1 FOR UPDATE`, principal.ID))
		if errors.Is(err, sql.ErrNoRows) {
			return ErrPrincipalNotFound
		}
		if err != nil {
			return err
		}
		if err := validateTokenEffectShape(*token); err != nil {
			return err
		}
		switch token.OAuthEffectState {
		case EffectManualRequired:
			if principal.State == PrincipalConnected {
				return ErrManualActionRequired
			}
			if token.RefreshOwnerID != "" || token.RefreshLeaseExpiresAt != nil {
				return ErrEffectStateInvalid
			}
		case EffectRefreshDispatched:
			if principal.State != PrincipalConnected || principal.Generation != token.PrincipalGeneration ||
				token.RefreshLeaseExpiresAt == nil || token.RefreshLeaseExpiresAt.After(now) {
				return ErrRefreshInProgress
			}
			result, err := tx.ExecContext(ctx, `
UPDATE _System_ConnectedPrincipal
   SET Generation = Generation + 1, State = ?, LastModifiedDate = ?
 WHERE Id = ? AND UserId = ? AND ConnectorKey = ? AND Generation = ? AND State = ?`,
				PrincipalReconnectRequired, now, principal.ID, principal.UserID,
				principal.ConnectorKey, principal.Generation, PrincipalConnected)
			if err != nil {
				return err
			}
			won, err := exactlyOneRow(result)
			if err != nil {
				return err
			}
			if !won {
				return ErrGenerationMismatch
			}
		case EffectRevokeDispatched:
			if principal.State == PrincipalConnected || token.RefreshLeaseExpiresAt == nil ||
				token.RefreshLeaseExpiresAt.After(now) {
				return ErrRefreshInProgress
			}
		default:
			return ErrManualActionRequired
		}
		event := cleanupAuditEvent(request, principal.ID, token.OAuthEffectState, token.OAuthEffectVersion, now)
		if err := audit.EmitOAuthCleanup(ctx, tx, event); err != nil {
			return err
		}
		query := `
DELETE FROM _System_ConnectedPrincipalToken
 WHERE ConnectedPrincipalId = ? AND PrincipalGeneration = ? AND TokenVersion = ?
	AND OAuthEffectState = ? AND OAuthEffectVersion = ?`
		args := []any{principal.ID, token.PrincipalGeneration, token.TokenVersion,
			token.OAuthEffectState, token.OAuthEffectVersion}
		if token.OAuthEffectState == EffectManualRequired {
			query += ` AND RefreshOwnerId IS NULL AND RefreshLeaseExpiresAt IS NULL`
		} else {
			query += ` AND RefreshOwnershipEpoch = ? AND BINARY RefreshOwnerId = BINARY ?
	   AND RefreshLeaseExpiresAt = ? AND RefreshLeaseExpiresAt <= ?
	   AND RefreshDispatchStartedAt = ? AND PendingTokenCiphertext IS NULL`
			args = append(args, token.RefreshOwnershipEpoch, token.RefreshOwnerID,
				token.RefreshLeaseExpiresAt, now, token.RefreshDispatchStartedAt)
		}
		result, err := tx.ExecContext(ctx, query, args...)
		if err != nil {
			return err
		}
		won, err := exactlyOneRow(result)
		if err != nil {
			return err
		}
		if !won {
			return ErrOwnershipLost
		}
		return nil
	})
}

func cleanupAuditEvent(request CleanupAttestation, recordID, effectState string, effectVersion int64, now time.Time) CleanupAuditEvent {
	eventID := digestString(fmt.Sprintf("external_cleanup_attested\x00%s\x00%s\x00%s\x00%d",
		request.UserID, request.ConnectorKey, recordID, effectVersion))
	return CleanupAuditEvent{EventID: eventID, UserID: request.UserID, ConnectorKey: request.ConnectorKey,
		EffectState: EffectCleanupAttested, PreviousEffectState: effectState,
		Reason: request.Reason, OccurredAt: now}
}
