package middleware

import (
	"context"
	"errors"
	"log/slog"
	"net/http"
	"strings"

	"qycw/go-server/internal/response"
)

type Principal struct {
	ID             int64
	Role           string
	User           any
	ImpersonatorID *int64
}

type Authenticator interface {
	Authenticate(ctx context.Context, rawAccessToken string) (Principal, error)
}

type principalContextKey struct{}

type Auth struct {
	authenticator Authenticator
	logger        *slog.Logger
}

func NewAuth(authenticator Authenticator, logger *slog.Logger) *Auth {
	return &Auth{authenticator: authenticator, logger: logger}
}

func (a *Auth) RequireUser(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		principal, ok := a.authenticate(w, r)
		if !ok {
			return
		}
		if principal.ImpersonatorID != nil && !isReadOnlyMethod(r.Method) {
			response.Detail(w, http.StatusForbidden, "Impersonation sessions are read-only")
			return
		}
		next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), principalContextKey{}, principal)))
	})
}

func isReadOnlyMethod(method string) bool {
	return method == http.MethodGet || method == http.MethodHead || method == http.MethodOptions
}

func (a *Auth) RequireRole(roles ...string) func(http.Handler) http.Handler {
	allowed := map[string]struct{}{}
	for _, role := range roles {
		allowed[role] = struct{}{}
	}
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			principal, ok := a.authenticate(w, r)
			if !ok {
				return
			}
			if _, exists := allowed[principal.Role]; !exists {
				response.Detail(w, http.StatusForbidden, "Insufficient permissions")
				return
			}
			next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), principalContextKey{}, principal)))
		})
	}
}

func PrincipalFromContext(ctx context.Context) (Principal, bool) {
	principal, ok := ctx.Value(principalContextKey{}).(Principal)
	return principal, ok
}

func (a *Auth) authenticate(w http.ResponseWriter, r *http.Request) (Principal, bool) {
	if a.authenticator == nil {
		response.Detail(w, http.StatusServiceUnavailable, "鉴权服务未启用")
		return Principal{}, false
	}
	token, ok := bearerToken(r.Header.Get("Authorization"))
	if !ok {
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return Principal{}, false
	}
	principal, err := a.authenticator.Authenticate(r.Context(), token)
	if err != nil {
		if a.logger != nil {
			a.logger.Debug("auth failed", "error", err)
		}
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return Principal{}, false
	}
	return principal, true
}

func bearerToken(header string) (string, bool) {
	const prefix = "Bearer "
	if !strings.HasPrefix(header, prefix) {
		return "", false
	}
	token := strings.TrimSpace(strings.TrimPrefix(header, prefix))
	return token, token != ""
}

func CurrentPrincipal(ctx context.Context) (Principal, error) {
	principal, ok := PrincipalFromContext(ctx)
	if !ok {
		return Principal{}, errors.New("principal not found in context")
	}
	return principal, nil
}
