package httpapi

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

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/identity"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

const (
	errorCodeGatewayUserNotProvisioned = "GATEWAY_USER_NOT_PROVISIONED"
	errorCodeGatewayUserDisabled       = "GATEWAY_USER_DISABLED"
	errorCodeGatewayTenantUnavailable  = "GATEWAY_TENANT_UNAVAILABLE"
	errorCodeGatewayProvisioningFailed = "GATEWAY_USER_PROVISIONING_FAILED"
)

type oidcUserResolver interface {
	ResolveOrProvisionOIDCUser(context.Context, store.ResolveOrProvisionOIDCUserInput) (store.ResolveOrProvisionOIDCUserResult, error)
}

func (s *Server) requireUser(permission auth.Permission, next http.Handler) http.Handler {
	return s.auth.Require(permission, s.resolveGatewayUser(next))
}

func (s *Server) resolveGatewayUser(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, ok := auth.UserFromContext(r.Context())
		if !ok || user == nil || !strings.EqualFold(strings.TrimSpace(user.Source), "oidc") {
			next.ServeHTTP(w, r)
			return
		}
		result, err := s.resolveOIDCUserProjection(r.Context(), r, user)
		if err != nil {
			s.writeOIDCUserResolutionError(w, r, err)
			return
		}
		if result.User == nil || strings.TrimSpace(result.User.GatewayUserID) == "" {
			s.writeOIDCUserResolutionError(w, r, errors.New("OIDC user resolver returned no local user"))
			return
		}
		if result.Created {
			s.logger.InfoContext(r.Context(), "OIDC gateway user provisioned",
				"gatewayUserId", result.User.GatewayUserID,
				"auditId", result.AuditID,
			)
		}
		next.ServeHTTP(w, r.WithContext(auth.WithUser(r.Context(), result.User)))
	})
}

func (s *Server) resolveOIDCUserProjection(ctx context.Context, r *http.Request, user *auth.User) (store.ResolveOrProvisionOIDCUserResult, error) {
	runtime := s.currentIdentityRuntime()
	if runtime == nil {
		return store.ResolveOrProvisionOIDCUserResult{}, errors.New("active identity runtime is unavailable")
	}
	return s.resolveOIDCUserProjectionForRevision(ctx, r, user, runtime.Revision)
}

func (s *Server) resolveOIDCUserProjectionForRevision(ctx context.Context, r *http.Request, user *auth.User, revision identity.Revision) (store.ResolveOrProvisionOIDCUserResult, error) {
	if s.oidcUserResolver == nil {
		return store.ResolveOrProvisionOIDCUserResult{}, errors.New("OIDC user resolver is unavailable")
	}
	return s.oidcUserResolver.ResolveOrProvisionOIDCUser(ctx, store.ResolveOrProvisionOIDCUserInput{
		Issuer:              revision.Issuer,
		Subject:             user.ID,
		Username:            user.Username,
		Roles:               user.Roles,
		TenantID:            user.TenantID,
		GatewayTenantKey:    revision.LocalTenantKey,
		ProvisioningEnabled: revision.JITEnabled,
		RequestIP:           limitAuditText(requestIP(r), 128),
		UserAgent:           limitAuditText(r.UserAgent(), 512),
	})
}

func (s *Server) writeOIDCUserResolutionError(w http.ResponseWriter, r *http.Request, err error) {
	switch {
	case errors.Is(err, store.ErrOIDCUserNotProvisioned):
		writeError(w, http.StatusForbidden, "该账号尚未开通 EasyAI Gateway", errorCodeGatewayUserNotProvisioned)
	case errors.Is(err, store.ErrOIDCUserDisabled):
		writeError(w, http.StatusForbidden, "该 Gateway 账号已停用，请联系管理员", errorCodeGatewayUserDisabled)
	case errors.Is(err, store.ErrOIDCTenantUnavailable):
		writeError(w, http.StatusServiceUnavailable, "Gateway 租户尚未就绪，请联系管理员", errorCodeGatewayTenantUnavailable)
	default:
		s.logger.ErrorContext(r.Context(), "resolve OIDC gateway user failed", "error", err, "path", r.URL.Path)
		writeError(w, http.StatusServiceUnavailable, "Gateway 账号初始化失败，请稍后重试", errorCodeGatewayProvisioningFailed)
	}
}

func limitAuditText(value string, limit int) string {
	value = strings.TrimSpace(value)
	runes := []rune(value)
	if limit > 0 && len(runes) > limit {
		return string(runes[:limit])
	}
	return value
}
