package middleware

import (
	"context"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap-inc/nexuscrm-api-host/hostruntime"
	apihostmiddleware "github.com/pingcap-inc/nexuscrm-api-host/middleware"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

// AuthorizingSessionMiddleware is the backend adapter for api-host's closed
// HTTP authorization module. One construction binds strong credential
// admission, one aggregate snapshot provider, the version-qualified cache,
// login warming, backend request context and cache invalidation.
type AuthorizingSessionMiddleware struct {
	inner  *apihostmiddleware.AuthorizingSessionMiddleware
	warmer *apihostmiddleware.PermissionStateWarmer
	cache  *apihostmiddleware.PermissionStateCache
}

// NewAuthorizingSessionMiddleware rejects incomplete composition before a
// production route can activate. Legacy SessionAuthenticator and
// PermissionProvider implementations cannot satisfy these parameters.
func NewAuthorizingSessionMiddleware(
	admitter hostruntime.AuthorizingSessionAdmitter,
	snapshotProvider hostruntime.EffectivePermissionSnapshotProvider,
	requestContext hostruntime.PermissionRequestContext,
	failureProjector func(
		c *gin.Context,
		status int,
		code string,
		message string,
	) (int, any),
	cacheTTL time.Duration,
	// sharedCache lets a second instance reuse the first one's snapshot cache. Pass nil to
	// create one.
	//
	// Two instances with two caches would need two invalidation registrations, and
	// permissions.SetCacheInvalidator takes one — so the second cache would keep serving
	// permissions that had already been revoked, for a whole TTL, on exactly the routes the
	// second instance guards. Sharing the object means invalidating either reaches both,
	// which is a property rather than a wiring step someone has to remember.
	sharedCache *apihostmiddleware.PermissionStateCache,
	// browserSessionLane admits a first-party request whose only credential is its
	// session-control proof. It is a parameter rather than a package default so a
	// deployment that has not opted into the browser session sees the admission behaviour
	// it has today, and so the decision has one visible owner at the call site.
	browserSessionLane bool,
) (*AuthorizingSessionMiddleware, error) {
	cache := sharedCache
	if cache == nil {
		created, err := apihostmiddleware.NewPermissionStateCache(cacheTTL)
		if err != nil {
			return nil, err
		}
		cache = created
	}
	inner, err := apihostmiddleware.NewAuthorizingSessionMiddleware(
		apihostmiddleware.AuthorizingSessionMiddlewareOptions{
			Admitter:           admitter,
			SnapshotProvider:   snapshotProvider,
			SnapshotCache:      cache,
			SessionWriter:      backendSessionContextWriter{},
			PermissionContext:  backendSessionContextWriter{},
			RequestContext:     requestContext,
			BrowserSessionLane: browserSessionLane,
			FailureResponder: backendAuthFailureResponder{
				projector: failureProjector,
			},
		},
	)
	if err != nil {
		return nil, err
	}
	warmer, err := apihostmiddleware.NewPermissionStateWarmer(
		cache,
		snapshotProvider,
		requestContext,
	)
	if err != nil {
		return nil, err
	}
	return &AuthorizingSessionMiddleware{
		inner:  inner,
		warmer: warmer,
		cache:  cache,
	}, nil
}

func (middleware *AuthorizingSessionMiddleware) RequireAuth(
	challengePolicies ...AuthChallengePolicy,
) gin.HandlerFunc {
	if middleware == nil || middleware.inner == nil {
		return unavailableAuthorizingSessionMiddleware
	}
	return middleware.inner.RequireAuth(challengePolicies...)
}

func (middleware *AuthorizingSessionMiddleware) OptionalAuth() gin.HandlerFunc {
	if middleware == nil || middleware.inner == nil {
		return unavailableAuthorizingSessionMiddleware
	}
	return middleware.inner.OptionalAuth()
}

// WarmUserAsync satisfies the OAuth login-time warming interface. Warming is
// best effort; request admission remains the correctness authority.
func (middleware *AuthorizingSessionMiddleware) WarmUserAsync(
	ctx context.Context,
	requestID string,
	userID string,
	profileID string,
) {
	if middleware == nil || middleware.warmer == nil {
		return
	}
	middleware.warmer.WarmUserAsync(ctx, requestID, userID, profileID)
}

// Invalidate and InvalidateUser satisfy permissions.PermissionCacheInvalidator.
// They affect only cache latency; admitted floors remain the correctness fence.
func (middleware *AuthorizingSessionMiddleware) Invalidate() {
	if middleware == nil || middleware.cache == nil {
		return
	}
	middleware.cache.Invalidate()
}

func (middleware *AuthorizingSessionMiddleware) InvalidateUser(userID string) {
	if middleware == nil || middleware.cache == nil {
		return
	}
	middleware.cache.InvalidateUser(userID)
}

func unavailableAuthorizingSessionMiddleware(c *gin.Context) {
	if c == nil {
		return
	}
	backendAuthFailureResponder{}.WriteAuthFailure(c, apihostmiddleware.AuthFailure{
		Status:  503,
		Code:    string(permissionstatev1.FailureAuthorityUnavailable),
		Message: "authorization authority unavailable",
	})
	c.Abort()
}

// SnapshotCache exposes the cache so a sibling instance can share it, and so a test can
// assert that two instances do.
func (middleware *AuthorizingSessionMiddleware) SnapshotCache() *apihostmiddleware.PermissionStateCache {
	if middleware == nil {
		return nil
	}
	return middleware.cache
}
