package iam

import (
	"context"
	"strings"

	"github.com/gpuaas/platform/packages/platform/registry"
)

type Backend interface {
	ResolveProjectScope(ctx context.Context, subjectUserID, projectID string) (*ProjectScope, error)
	ResolveOrgScope(ctx context.Context, subjectUserID, orgID string) (*OrgScope, error)
	ResolvePlatformRoles(ctx context.Context, principalID, legacyUsersRole string) ([]PlatformRole, error)
	EvaluateRolePermission(roles []string, action Action) Decision
}

type ScopeRegistry interface {
	GetScope(ctx context.Context, scopeID string) (*registry.Scope, error)
}

type Service struct {
	backend       Backend
	scopeRegistry ScopeRegistry
}

func NewService(backend Backend) *Service {
	return &Service{backend: backend}
}

func NewServiceWithScopeRegistry(backend Backend, scopeRegistry ScopeRegistry) *Service {
	return &Service{backend: backend, scopeRegistry: scopeRegistry}
}

func (s *Service) ResolveProjectScope(ctx context.Context, subjectUserID, projectID string) (*ProjectScope, error) {
	subjectUserID = strings.TrimSpace(subjectUserID)
	projectID = strings.TrimSpace(projectID)
	if subjectUserID == "" || projectID == "" {
		return nil, ErrInvalidInput
	}
	if s == nil || s.backend == nil {
		return nil, ErrUnavailable
	}
	return s.backend.ResolveProjectScope(ctx, subjectUserID, projectID)
}

func (s *Service) ResolveOrgScope(ctx context.Context, subjectUserID, orgID string) (*OrgScope, error) {
	subjectUserID = strings.TrimSpace(subjectUserID)
	orgID = strings.TrimSpace(orgID)
	if subjectUserID == "" || orgID == "" {
		return nil, ErrInvalidInput
	}
	if s == nil || s.backend == nil {
		return nil, ErrUnavailable
	}
	return s.backend.ResolveOrgScope(ctx, subjectUserID, orgID)
}

func (s *Service) ResolvePlatformRoles(ctx context.Context, principalID, legacyUsersRole string) ([]PlatformRole, error) {
	principalID = strings.TrimSpace(principalID)
	legacyUsersRole = strings.TrimSpace(legacyUsersRole)
	if principalID == "" {
		return nil, ErrInvalidInput
	}
	if s == nil || s.backend == nil {
		return nil, ErrUnavailable
	}
	return s.backend.ResolvePlatformRoles(ctx, principalID, legacyUsersRole)
}

func (s *Service) EvaluateRolePermission(roles []string, action Action) Decision {
	if action == "" {
		return Decision{
			Allow:        false,
			ReasonCode:   ReasonPermissionDenied,
			AppliedScope: ScopeGlobal,
			PolicySource: PolicySourceInCode,
		}
	}
	if s == nil || s.backend == nil {
		return Decision{
			Allow:        false,
			ReasonCode:   ReasonPermissionDenied,
			AppliedScope: ScopeGlobal,
			PolicySource: PolicySourceInCode,
		}
	}
	return s.backend.EvaluateRolePermission(roles, action)
}

func (s *Service) EvaluateScopePermission(ctx context.Context, input ScopeDecisionInput) Decision {
	input.RequiredScope = strings.TrimSpace(input.RequiredScope)
	input.RequiredAction = Action(strings.TrimSpace(string(input.RequiredAction)))
	input.ActorID = strings.TrimSpace(input.ActorID)
	if input.RequiredScope == "" || input.RequiredAction == "" || input.ActorType == "" || input.ActorID == "" {
		return deny(ReasonPermissionDenied, input.AppliedScope, PolicySourceInCode)
	}
	if s == nil || s.backend == nil || s.scopeRegistry == nil {
		return deny(ReasonPermissionDenied, input.AppliedScope, PolicySourceInCode)
	}
	scope, err := s.scopeRegistry.GetScope(ctx, input.RequiredScope)
	if err != nil || scope == nil {
		return deny(ReasonScopeMismatch, input.AppliedScope, PolicySourceInCode)
	}
	if scope.Lifecycle != registry.LifecycleActive {
		return deny(ReasonPolicyConstraintDeny, input.AppliedScope, PolicySourcePolicyValues)
	}
	if !actorTypeAllowed(input.ActorType, scope.AllowedActorTypes) {
		return deny(ReasonScopeMismatch, input.AppliedScope, PolicySourceInCode)
	}
	decision := s.backend.EvaluateRolePermission(input.Roles, input.RequiredAction)
	if !decision.Allow {
		return decision
	}
	decision.AppliedScope = normalizeAppliedScope(input.AppliedScope)
	decision.PolicySource = PolicySourcePolicyValues
	return decision
}

func actorTypeAllowed(actorType ActorType, allowed []string) bool {
	actor := strings.TrimSpace(string(actorType))
	for _, candidate := range allowed {
		if strings.EqualFold(strings.TrimSpace(candidate), actor) {
			return true
		}
	}
	return false
}

func deny(reason ReasonCode, scope AppliedScope, source PolicySource) Decision {
	return Decision{
		Allow:        false,
		ReasonCode:   reason,
		AppliedScope: normalizeAppliedScope(scope),
		PolicySource: source,
	}
}

func normalizeAppliedScope(scope AppliedScope) AppliedScope {
	if scope == "" {
		return ScopeGlobal
	}
	return scope
}
