package connector_oauth

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

const (
	PrincipalConnected         = "connected"
	PrincipalDisconnected      = "disconnected"
	PrincipalReconnectRequired = "reconnect_required"

	EffectNone                 = "none"
	EffectExchangeClaimed      = "exchange_claimed"
	EffectExchangeDispatched   = "exchange_dispatched"
	EffectExchangeResultStaged = "exchange_result_staged"
	EffectRefreshClaimed       = "refresh_claimed"
	EffectRefreshDispatched    = "refresh_dispatched"
	EffectRefreshResultStaged  = "refresh_result_staged"
	EffectRevokePending        = "revoke_pending"
	EffectRevokeClaimed        = "revoke_claimed"
	EffectRevokeDispatched     = "revoke_dispatched"
	EffectRevokeSucceeded      = "revoke_succeeded"
	EffectManualRequired       = "manual_required"
	EffectCleanupAttested      = "external_cleanup_attested"

	DefaultAuthorizationTTL = 10 * time.Minute
	DefaultRefreshLeaseTTL  = 2 * time.Minute
)

var (
	ErrInvalidInput                = errors.New("connected OAuth input is invalid")
	ErrDependencyMissing           = errors.New("connected OAuth dependency is missing")
	ErrAuthorizationDenied         = errors.New("connected authorization is denied")
	ErrAuthorizationExpired        = errors.New("connected authorization is expired")
	ErrAuthorizationProgress       = errors.New("connected authorization is already in progress")
	ErrAuthorizationCleanupPending = errors.New("connected authorization cleanup outcome is unconfirmed")
	ErrPrincipalNotFound           = errors.New("connected principal was not found")
	ErrPrincipalOwnership          = errors.New("connected principal belongs to another user")
	ErrGenerationMismatch          = errors.New("connected principal generation does not match")
	ErrReconnectRequired           = errors.New("connected principal must reconnect")
	ErrDisconnectRequired          = errors.New("connected principal must disconnect before authorization")
	ErrRefreshInProgress           = errors.New("connected token refresh is already in progress")
	ErrOwnershipLost               = errors.New("connected OAuth action ownership was lost")
	ErrEffectResultStaged          = errors.New("connected OAuth provider result was staged")
	ErrManualActionRequired        = errors.New("connected OAuth action requires manual cleanup attestation")
	ErrEffectStateInvalid          = errors.New("connected OAuth effect state is invalid")
	ErrEncryptionKeyMissing        = errors.New("connected OAuth encryption key is unavailable")
	ErrCiphertextInvalid           = errors.New("connected OAuth ciphertext is invalid")
	ErrScopeMismatch               = errors.New("connected principal scopes are insufficient")
)

type SecretPurpose string

const (
	SecretAuthorizationVerifier SecretPurpose = "authorization_verifier"
	SecretPrincipalAccessToken  SecretPurpose = "principal_access_token"
	SecretPrincipalRefreshToken SecretPurpose = "principal_refresh_token"
	SecretAuthorizationResult   SecretPurpose = "authorization_result"
	SecretRefreshResult         SecretPurpose = "refresh_result"
)

// SecretContext is authenticated with each ciphertext. Generation and Version
// are immutable source coordinates, never a mutable lease owner or epoch.
type SecretContext struct {
	Purpose      SecretPurpose
	UserID       string
	ConnectorKey string
	RecordID     string
	Generation   int64
	Version      int64
}

type SecretCipher interface {
	Seal(plaintext []byte, context SecretContext) (ciphertext string, keyID string, err error)
	Open(ciphertext string, context SecretContext) ([]byte, error)
}

type ProviderErrorKind string

const (
	ProviderErrorAuthorization ProviderErrorKind = "authorization"
	ProviderErrorRejected      ProviderErrorKind = "rejected"
	// ProviderErrorTransient means the adapter proved that Exchange produced no
	// uncertain token effect. It does not permit transparent retry.
	ProviderErrorTransient ProviderErrorKind = "transient"
	// ProviderErrorUncertain means the operation may have produced a remote
	// effect, so automatic retry is forbidden and manual cleanup may be needed.
	ProviderErrorUncertain ProviderErrorKind = "uncertain"
)

// ProviderError is the only provider failure allowed across the package
// boundary. It deliberately has no wrapped cause, body, header, or token.
type ProviderError struct {
	Operation string
	Kind      ProviderErrorKind
	Code      string
}

func (e *ProviderError) Error() string {
	if e == nil {
		return "connected provider operation failed"
	}
	return "connected provider " + safeOperation(e.Operation) + " failed: " + safeProviderCode(e.Code)
}

type AuthorizationURLRequest struct {
	State           string
	CodeChallenge   string
	ChallengeMethod string
	RedirectURI     string
	Scopes          []string
}

type TokenExchangeRequest struct {
	Code         string
	CodeVerifier string
	RedirectURI  string
}

type TokenSet struct {
	AccessToken  string
	RefreshToken string
	TokenType    string
	// Scopes is nil only when the provider omitted scope from its response.
	// Providers use a non-nil empty slice to report an explicit empty grant.
	Scopes    []string
	ExpiresAt *time.Time
}

type RemoteIdentity struct {
	Issuer           string
	Subject          string
	PrincipalAddress string
	DisplayName      string
}

// OAuthProvider must perform at most the single call requested by each method.
// EffectTerminationBound is the maximum time a side-effecting method may take to
// return after its context is canceled. That bound covers all local and remote
// work capable of producing the requested effect. Implementations must not
// detach requests, retry transparently, or allow effect-producing work to
// continue after returning. AuthorizationURL has no token-side effect. Identity
// is read-only.
type OAuthProvider interface {
	EffectTerminationBound() time.Duration
	AuthorizationURL(AuthorizationURLRequest) (string, error)
	Exchange(context.Context, TokenExchangeRequest) (TokenSet, error)
	Identity(context.Context, string) (RemoteIdentity, error)
	Refresh(context.Context, string) (TokenSet, error)
	Revoke(context.Context, string) error
}

type ProviderResolver interface {
	Provider(connectorKey string) (OAuthProvider, bool)
}

// AuthorizationPolicy is mandatory. Redirect and scope policy is supplied by
// the integration composition rather than inferred from caller input.
type AuthorizationPolicy interface {
	ValidateRedirect(connectorKey, redirectURI string) error
	ValidateScopes(connectorKey string, scopes []string) error
}

type ConnectedPrincipal struct {
	ID               string
	UserID           string
	ConnectorKey     string
	Generation       int64
	Issuer           string
	Subject          string
	PrincipalAddress string
	DisplayName      string
	State            string
	ConnectedAt      time.Time
	DisconnectedAt   *time.Time
	CreatedDate      time.Time
	LastModifiedDate time.Time
}

type AuthorizationStart struct {
	URL                string
	ExpiresAt          time.Time
	ExpectedGeneration int64
	Principal          *ConnectedPrincipal
	Outcome            string
}

const (
	AuthorizationOutcomeRedirect  = "redirect_required"
	AuthorizationOutcomeConnected = "connected"
)

type AuthorizationCompletion struct {
	Outcome string
}

const AuthorizationCompletionResultStaged = "result_staged"

type BeginAuthorizationRequest struct {
	UserID       string
	ConnectorKey string
	RedirectURI  string
	Scopes       []string
}

type CompleteAuthorizationRequest struct {
	UserID string
	State  string
	Code   string
}

type ResolveAccessTokenRequest struct {
	UserID             string
	ConnectorKey       string
	ExpectedGeneration int64
}

// ResolveScopedAccessTokenRequest adds a scope constraint without changing the
// released positional shape of ResolveAccessTokenRequest.
type ResolveScopedAccessTokenRequest struct {
	UserID             string
	ConnectorKey       string
	ExpectedGeneration int64
	RequiredScopes     []string
}

type EffectStatus struct {
	UserID         string
	ConnectorKey   string
	PrincipalState string
	EffectState    string
	CanAttest      bool
}

type CleanupAttestation struct {
	UserID       string
	ConnectorKey string
	Reason       string
}

type CleanupAuditEvent struct {
	EventID             string
	UserID              string
	ConnectorKey        string
	EffectState         string
	PreviousEffectState string
	Reason              string
	OccurredAt          time.Time
}

type CleanupAuditEmitter interface {
	EmitOAuthCleanup(context.Context, *sql.Tx, CleanupAuditEvent) error
}
