package hostruntime

import (
	"context"
	"errors"
	"time"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

// ErrAuthorizingCredentialRejected is the only admission error that optional
// auth may treat as anonymous. Every other admission error fails closed as an
// authority outage.
var ErrAuthorizingCredentialRejected = errors.New("authorizing credential rejected")

// AuthorizingSessionMode selects one closed credential-admission policy.
type AuthorizingSessionMode uint8

const (
	AuthorizingSessionStrict AuthorizingSessionMode = iota + 1
	AuthorizingSessionRenewal
	AuthorizingSessionControl

	// AuthorizingSessionBrowser admits a first-party browser request from its
	// session-control proof alone, with no bearer anywhere in the exchange.
	//
	// It is not a weaker Control. Control carries a bearer and treats the proof as a
	// second factor; this carries no bearer at all, which is the point — the page holds
	// no credential it can read. Implementations resolve the same authoritative Subject
	// and Permission State floor as every other mode, so a browser request is admitted
	// with the identical authorization result rather than through a path that skips it.
	//
	// The admission may not return a replacement bearer. There is no credential to
	// replace, and producing one would hand the page exactly what this mode exists to
	// withhold.
	AuthorizingSessionBrowser
)

// Valid reports whether the mode is part of the closed admission contract.
func (mode AuthorizingSessionMode) Valid() bool {
	switch mode {
	case AuthorizingSessionStrict, AuthorizingSessionRenewal, AuthorizingSessionControl,
		AuthorizingSessionBrowser:
		return true
	default:
		return false
	}
}

// AuthorizingSessionRequest carries one credential through a strong admission policy.
//
// ControlProof is accepted in control mode, where it accompanies a bearer, and in browser
// mode, where it is the whole credential and BearerToken is empty.
type AuthorizingSessionRequest struct {
	BearerToken  string
	ControlProof string
	Mode         AuthorizingSessionMode
}

// AuthorizingSessionAdmission binds source-compatible claims to the current
// authoritative subject and Permission State floor observed during admission.
// Credential replacement fields preserve the existing HTTP renewal protocol.
type AuthorizingSessionAdmission struct {
	Claims               *SessionClaims
	Subject              permissionstatev1.Subject
	PermissionFloor      permissionstatev1.Version
	RefreshedBearerToken string
	ProofCookieExpiresAt time.Time
}

// AuthorizingSessionAdmitter is an additive strong capability. It deliberately
// does not embed SessionAuthenticator: new authorization composition cannot
// satisfy this interface with the legacy claim-only validator. Implementations
// return ErrAuthorizingCredentialRejected only for invalid credentials; every
// other error is treated as inability to establish current authority.
type AuthorizingSessionAdmitter interface {
	AdmitAuthorizingSession(
		ctx context.Context,
		request AuthorizingSessionRequest,
	) (AuthorizingSessionAdmission, error)
	TouchSession(ctx context.Context, sessionID string)
}

// EffectivePermissionSnapshotProvider loads one current aggregate snapshot for
// the authoritative subject. Implementations must return a validated snapshot
// or an error; partial permission families are never valid results.
type EffectivePermissionSnapshotProvider interface {
	LoadCurrentEffectivePermissionSnapshot(
		ctx context.Context,
		subject permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error)
}

// AuthorizingPermissionState is the admitted subject, required floor, and
// resolved aggregate installed for one request.
type AuthorizingPermissionState struct {
	Subject  permissionstatev1.Subject
	Floor    permissionstatev1.Version
	Snapshot permissionstatev1.EffectiveSnapshot
}

type authorizingPermissionStateContextKey struct{}

// ContextWithAuthorizingPermissionState validates and installs one complete
// request authorization state. A partial or stale aggregate is rejected.
func ContextWithAuthorizingPermissionState(
	ctx context.Context,
	state AuthorizingPermissionState,
) (context.Context, error) {
	if ctx == nil {
		return nil, &permissionstatev1.Failure{
			Category: permissionstatev1.FailureInvalidValue,
		}
	}
	if err := state.Subject.Validate(); err != nil {
		return nil, err
	}
	if err := state.Floor.Validate(); err != nil {
		return nil, err
	}
	if err := state.Snapshot.Validate(); err != nil {
		return nil, err
	}
	satisfies, err := state.Snapshot.Version().Satisfies(state.Floor)
	if err != nil {
		return nil, err
	}
	if !satisfies {
		return nil, &permissionstatev1.Failure{
			Category: permissionstatev1.FailureStaleSnapshot,
		}
	}
	return context.WithValue(ctx, authorizingPermissionStateContextKey{}, state), nil
}

// AuthorizingPermissionStateFromContext returns the complete strong state
// installed by api-host middleware.
func AuthorizingPermissionStateFromContext(
	ctx context.Context,
) (AuthorizingPermissionState, bool) {
	if ctx == nil {
		return AuthorizingPermissionState{}, false
	}
	state, ok := ctx.Value(authorizingPermissionStateContextKey{}).(AuthorizingPermissionState)
	return state, ok
}
