package middleware

import (
	"errors"
	"net/http"
	"slices"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/config"
	"hyapp-admin-server/internal/model"
	"hyapp-admin-server/internal/repository"
	"hyapp-admin-server/internal/response"
	"hyapp-admin-server/internal/service"

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

type AppAccessStore interface {
	AppAccessForUser(userID uint) (repository.AppAccess, error)
}

type AuthorizationStore interface {
	CurrentAuthorizationForUser(userID uint) (repository.UserAuthorization, error)
}

const (
	ContextUserID      = "userID"
	ContextUsername    = "username"
	ContextPermissions = "permissions"
)

func CORS(cfg config.Config) gin.HandlerFunc {
	allowed := map[string]struct{}{}
	for _, origin := range cfg.CORSAllowedOrigins {
		allowed[origin] = struct{}{}
	}

	return func(c *gin.Context) {
		origin := c.Request.Header.Get("Origin")
		if _, ok := allowed[origin]; ok {
			c.Header("Access-Control-Allow-Origin", origin)
			c.Header("Vary", "Origin")
			c.Header("Access-Control-Allow-Credentials", "true")
			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-ID, "+appctx.HeaderAppCode+", X-CSRF-Token")
			c.Header("Access-Control-Expose-Headers", "X-Request-ID, X-CSRF-Token")
			c.Header("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS")
		}

		if c.Request.Method == http.MethodOptions {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}

		c.Next()
	}
}

func AppCode() gin.HandlerFunc {
	return func(c *gin.Context) {
		appCode := appctx.Normalize(c.GetHeader(appctx.HeaderAppCode))
		c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), appCode))
		c.Header(appctx.HeaderAppCode, appCode)
		c.Next()
	}
}

func AuthRequired(auth *service.AuthService, store AuthorizationStore) gin.HandlerFunc {
	return func(c *gin.Context) {
		header := c.GetHeader("Authorization")
		if !strings.HasPrefix(header, "Bearer ") {
			response.Unauthorized(c, "缺少访问凭证")
			c.Abort()
			return
		}

		if auth == nil || store == nil {
			// Authorization cannot safely fall back to claims.Permissions: doing so
			// would restore removed privileges until the access token expires.
			response.ServerError(c, "认证权限服务不可用")
			c.Abort()
			return
		}
		claims, err := auth.ParseAccessToken(strings.TrimPrefix(header, "Bearer "))
		if err != nil {
			response.Unauthorized(c, "访问凭证已失效")
			c.Abort()
			return
		}

		authorization, err := store.CurrentAuthorizationForUser(claims.UserID)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				response.Unauthorized(c, "用户不存在")
			} else {
				// Database errors fail closed. A stale JWT permission snapshot is not
				// an acceptable availability fallback for privileged admin actions.
				response.ServerError(c, "校验登录权限失败")
			}
			c.Abort()
			return
		}
		if authorization.Status != model.UserStatusActive {
			response.Unauthorized(c, "账号不可登录")
			c.Abort()
			return
		}

		c.Set(ContextUserID, claims.UserID)
		c.Set(ContextUsername, authorization.Username)
		c.Set(ContextPermissions, authorization.Permissions)
		c.Next()
	}
}

func RequireAppScope(store AppAccessStore) gin.HandlerFunc {
	return func(c *gin.Context) {
		// App 级路由必须显式携带 header，不能复用 appctx 的 lalu 默认值；否则无 App 用户省略 header 仍会落入 Lalu 数据域。
		appCode := strings.ToLower(strings.TrimSpace(c.GetHeader(appctx.HeaderAppCode)))
		if appCode == "" {
			response.Forbidden(c, "当前用户没有可访问的 App")
			c.Abort()
			return
		}
		if store == nil {
			response.ServerError(c, "App 权限服务不可用")
			c.Abort()
			return
		}
		access, err := store.AppAccessForUser(CurrentUserID(c))
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				response.Unauthorized(c, "用户不存在")
			} else {
				response.ServerError(c, "校验 App 权限失败")
			}
			c.Abort()
			return
		}
		if !access.Allows(appCode) {
			response.Forbidden(c, "当前用户无权访问该 App")
			c.Abort()
			return
		}
		c.Next()
	}
}

func RequirePermission(code string) gin.HandlerFunc {
	return func(c *gin.Context) {
		if HasPermission(c, code) {
			c.Next()
			return
		}

		response.Forbidden(c, "没有操作权限")
		c.Abort()
	}
}

// RequireAnyPermission is used by transitional RBAC routes where the new
// button-level permission and the old broad permission must both work.
func RequireAnyPermission(codes ...string) gin.HandlerFunc {
	return func(c *gin.Context) {
		if HasAnyPermission(c, codes...) {
			c.Next()
			return
		}

		response.Forbidden(c, "没有操作权限")
		c.Abort()
	}
}

// HasAnyPermission keeps privilege checks in handlers readable when one action
// is allowed by multiple compatible permission codes.
func HasAnyPermission(c *gin.Context, codes ...string) bool {
	for _, code := range codes {
		if HasPermission(c, code) {
			return true
		}
	}
	return false
}

// HasPermission reads the request-scoped permission list resolved by
// AuthRequired from current database state. JWT permission claims are retained
// for wire compatibility only and are never trusted for server authorization.
func HasPermission(c *gin.Context, code string) bool {
	return slices.Contains(CurrentPermissions(c), code)
}

func CurrentUserID(c *gin.Context) uint {
	value, ok := c.Get(ContextUserID)
	if !ok {
		return 0
	}
	id, ok := value.(uint)
	if !ok {
		return 0
	}
	return id
}

func CurrentUsername(c *gin.Context) string {
	value, ok := c.Get(ContextUsername)
	if !ok {
		return ""
	}
	username, ok := value.(string)
	if !ok {
		return ""
	}
	return username
}

func CurrentPermissions(c *gin.Context) []string {
	value, ok := c.Get(ContextPermissions)
	if !ok {
		return nil
	}
	permissions, ok := value.([]string)
	if !ok {
		return nil
	}
	return permissions
}
