package middleware

import (
	"net/http"

	"gateway/internal/library/actor"
	errs "gateway/internal/library/errors"
	"gateway/internal/library/errors/code"
	domperm "gateway/internal/model/permission/domain/usecase"
	"gateway/internal/response"

	"github.com/zeromicro/go-zero/core/logx"
)

// CasbinRBACOptions tunes the enforcement middleware.
type CasbinRBACOptions struct {
	// PlatformAdminRoleKey short-circuits enforcement when the actor's
	// auth context flags them as platform admin (handled upstream); the
	// value is the role key seeded for that role (e.g.
	// "platform_super_admin"). Empty disables the bypass.
	PlatformAdminRoleKey string

	// SkipPaths is an exact-match allowlist (e.g. /api/v1/health).
	// Useful for opting out specific routes when the middleware is
	// mounted on a wider group.
	SkipPaths map[string]struct{}
}

// CasbinRBACMiddleware enforces (tenant, uid, path, method) against
// the Casbin policy loaded by the permission module. It MUST be
// preceded by AuthJWTMiddleware in the chain so the actor is already
// present on the request context (via library/actor).
//
// Mounted via @server(middleware: AuthJWT,CasbinRBAC). Missing actor
// is treated as a hard 401 (the chain order guarantees AuthJWT runs
// first); a successful actor with no matching policy returns 403
// (28505000 in Permission scope).
//
// File name follows goctl's stringx convention (`casbinrbac_middleware.go`)
// so `make gen-api` sees it as already-generated and never overwrites
// the implementation.
type CasbinRBACMiddleware struct {
	rbac domperm.RBACUseCase
	opts CasbinRBACOptions
}

// NewCasbinRBACMiddleware wires the middleware with the permission
// module's RBACUseCase. A nil rbac transparently allows requests
// through (useful while the module is being rolled out).
func NewCasbinRBACMiddleware(rbac domperm.RBACUseCase, opts CasbinRBACOptions) *CasbinRBACMiddleware {
	if opts.SkipPaths == nil {
		opts.SkipPaths = map[string]struct{}{}
	}
	return &CasbinRBACMiddleware{rbac: rbac, opts: opts}
}

// Handle implements the go-zero rest.Middleware signature.
func (m *CasbinRBACMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
	bld := errs.For(code.Permission)
	return func(w http.ResponseWriter, r *http.Request) {
		if m.rbac == nil {
			next(w, r)
			return
		}
		if _, ok := m.opts.SkipPaths[r.URL.Path]; ok {
			next(w, r)
			return
		}
		caller, err := actor.ActorFromContext(r.Context())
		if err != nil {
			response.Write(r.Context(), w, nil,
				bld.AuthUnauthorized("missing actor for rbac check").WithCause(err))
			return
		}
		result, err := m.rbac.Check(r.Context(), &domperm.CheckRequest{
			TenantID: caller.TenantID,
			UID:      caller.UID,
			Path:     r.URL.Path,
			Method:   r.Method,
		})
		if err != nil {
			logx.WithContext(r.Context()).Errorf(
				"casbin: enforce error tenant=%s uid=%s path=%s method=%s: %v",
				caller.TenantID, caller.UID, r.URL.Path, r.Method, err)
			response.Write(r.Context(), w, nil,
				bld.SysInternal("casbin enforce failed").WithCause(err))
			return
		}
		if !result.Allow {
			response.Write(r.Context(), w, nil,
				bld.AuthForbidden("rbac denied"))
			return
		}
		next(w, r)
	}
}
