package middleware

import (
	"context"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/kernel/pkg/apierr"
	"github.com/nexuscrm/kernel/pkg/auth"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/pingcap-inc/nexuscrm-api-host/hostruntime"
	apihostmiddleware "github.com/pingcap-inc/nexuscrm-api-host/middleware"
)

type AuthChallengeContext = apihostmiddleware.AuthChallengeContext
type AuthChallenge = apihostmiddleware.AuthChallenge
type AuthChallengePolicy = apihostmiddleware.AuthChallengePolicy
type AuthChallengePolicyFunc = apihostmiddleware.AuthChallengePolicyFunc

// RequireAuth is a middleware that validates JWT tokens. authFailureProjector is
// the L1-supplied projection for the authentication-failure wire body; when nil,
// L0 emits a neutral auth-failure envelope. App-blind L0 never knows the
// product's wire vocabulary (e.g. the Salesforce v63 error-array shape).
func RequireAuth(authSvc hostruntime.SessionAuthenticator, authFailureProjector func(c *gin.Context, status int, code, message string) (int, any), challengePolicies ...AuthChallengePolicy) gin.HandlerFunc {
	return apihostmiddleware.RequireAuth(authSvc, backendSessionContextWriter{}, backendAuthFailureResponder{projector: authFailureProjector}, challengePolicies...)
}

type backendAuthFailureResponder struct {
	// projector is the L1-supplied auth-failure wire projection. When nil, L0
	// emits a neutral envelope. App-blind L0 never authors the product's wire
	// vocabulary (e.g. the Salesforce v63 error-array shape).
	projector func(c *gin.Context, status int, code, message string) (int, any)
}

func (r backendAuthFailureResponder) WriteAuthFailure(c *gin.Context, failure apihostmiddleware.AuthFailure) {
	status := failure.Status
	if status == 0 {
		status = http.StatusUnauthorized
	}
	code := strings.TrimSpace(failure.Code)
	message := strings.TrimSpace(failure.Message)
	if message == "" {
		message = "Session expired or invalid"
	}
	if r.projector != nil {
		s, body := r.projector(c, status, code, message)
		c.JSON(s, body)
		return
	}
	// Neutral L0 default: a product-agnostic error envelope. The product-specific
	// wire vocabulary (codes, array shape) is supplied by the L1 projector above.
	if code == "" {
		code = "UNAUTHENTICATED"
	}
	c.JSON(status, apierr.New(status, code, message))
}

type backendSessionContextWriter struct{}

func (backendSessionContextWriter) WriteSession(c *gin.Context, user hostruntime.UserSession, sessionID string) {
	// NEVER stash the raw bearer token. Downstream capture middleware would
	// otherwise persist it in `_System_RequestCapture.SessionId`, making the
	// capture table a credential dump. Expose only the JWT/session identifier.
	c.Set(constants.ContextKeyUser, authUserSessionFromHost(user))
	c.Set(constants.ContextKeySessionID, sessionID)
	// The identity this request is throttled and audited as. Written here because this is the one
	// point that holds both the credential class and the session id, and a PAT's session id is the
	// token id — the subject has to be the token, not its owner, or two tokens minted by one user
	// would share an allowance.
	if subject := RateLimitSubjectForSession(user, sessionID); subject != "" {
		c.Set(ContextKeyRateLimitSubject, subject)
	}
	if c.Request != nil {
		requestContext := context.WithValue(
			c.Request.Context(),
			constants.ContextKeySessionID,
			sessionID,
		)
		c.Request = c.Request.WithContext(requestContext)
	}
}

func authUserSessionFromHost(user hostruntime.UserSession) auth.UserSession {
	return auth.UserSession{
		ID:                user.ID,
		Name:              user.Name,
		Email:             user.Email,
		ProfileId:         user.ProfileID,
		ProfileName:       user.ProfileName,
		RoleId:            user.RoleID,
		RoleName:          user.RoleName,
		ConnectedAppId:    user.ConnectedAppID,
		Scope:             user.Scope,
		GrantType:         user.GrantType,
		SessionClass:      user.SessionClass,
		SystemPermissions: user.SystemPermissions,
	}
}
