package middleware

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
)

// ScopeRouteRequest is the host-level route decision passed to the policy
// owner. The api-host middleware owns request enforcement; the embedding host
// owns custom scope policy storage and interpretation.
type ScopeRouteRequest struct {
	Scope          string
	ConnectedAppID string
	Method         string
	FullPath       string
}

type ScopeRouteAuthorizer interface {
	AllowsScopedRoute(ScopeRouteRequest) bool
}

type ScopeRouteAuthorizerFunc func(ScopeRouteRequest) bool

func (f ScopeRouteAuthorizerFunc) AllowsScopedRoute(req ScopeRouteRequest) bool {
	if f == nil {
		return false
	}
	return f(req)
}

// ScopeEnforcerWithSessionContext checks whether the current OAuth session's
// scope allows the requested route. It stays storage-neutral: custom and
// product scope decisions are delegated to the supplied authorizer.
func ScopeEnforcerWithSessionContext(sessionContext ScopeSessionContext, authorizer ScopeRouteAuthorizer) gin.HandlerFunc {
	sessionContext = NormalizeScopeSessionContext(sessionContext)
	return func(c *gin.Context) {
		session := sessionContext.ReadSession(c)
		if !session.Exists || !session.Valid {
			c.Next()
			return
		}

		user := session.User
		if strings.TrimSpace(user.Scope) == "" {
			c.Next()
			return
		}

		if !scopeCoversRoute(strings.Fields(user.Scope), c, authorizer, user.ConnectedAppID) {
			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
				"errorCode": "INSUFFICIENT_SCOPE",
				"message":   "This session is not valid for use with the requested API",
			})
			return
		}

		sessionContext.WriteConnectedAppID(c, user.ConnectedAppID)
		c.Next()
	}
}

func scopeCoversRoute(scopes []string, c *gin.Context, authorizer ScopeRouteAuthorizer, connectedAppID string) bool {
	fullPath := c.FullPath()
	for _, rawScope := range scopes {
		scope := strings.ToLower(strings.TrimSpace(rawScope))
		switch scope {
		case "api", "full":
			return true
		case "api.read":
			if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead {
				return true
			}
			continue
		case "openid":
			if strings.Contains(fullPath, "/userinfo") {
				return true
			}
		}
		if authorizer != nil && authorizer.AllowsScopedRoute(ScopeRouteRequest{
			Scope:          scope,
			ConnectedAppID: strings.TrimSpace(connectedAppID),
			Method:         c.Request.Method,
			FullPath:       fullPath,
		}) {
			return true
		}
	}
	return false
}
