package rbac

import (
	"context"
	"errors"
	"strings"

	"github.com/google/uuid"
)

var (
	ErrTenantNotFound       = errors.New("tenant not found")
	ErrNamespaceNotFound    = errors.New("namespace not found")
	ErrRoleNotFound         = errors.New("role not found")
	ErrPermissionDenied     = errors.New("permission denied")
	ErrInvalidNamespace     = errors.New("invalid namespace access")
	ErrDataCategoryBlocked  = errors.New("data category blocked by policy")
	ErrLLMQuotaExceeded     = errors.New("LLM quota exceeded")
	ErrModelNotAllowed      = errors.New("model not allowed by policy")
)

// Service provides RBAC business logic
type Service struct {
	store *Store
}

// NewService creates a new RBAC service
func NewService(store *Store) *Service {
	return &Service{store: store}
}

// GetEffectivePermissions computes all effective permissions for a user
func (s *Service) GetEffectivePermissions(ctx context.Context, userID, tenantID uuid.UUID, namespaceID *uuid.UUID) (*EffectivePermissions, error) {
	// Get user roles
	userRoles, err := s.store.GetUserRolesForNamespace(ctx, userID, tenantID, namespaceID)
	if err != nil {
		return nil, err
	}

	// Aggregate permissions and roles
	permissionSet := make(map[string]bool)
	roleSet := make(map[string]bool)

	for _, ur := range userRoles {
		roleSet[ur.RoleName] = true
		for _, perm := range ur.RolePermissions {
			permissionSet[perm] = true
		}
	}

	// Convert to slices
	permissions := make([]string, 0, len(permissionSet))
	for perm := range permissionSet {
		permissions = append(permissions, perm)
	}

	roles := make([]string, 0, len(roleSet))
	for role := range roleSet {
		roles = append(roles, role)
	}

	// Get effective LLM policy
	llmPolicy, _ := s.store.GetEffectiveLLMPolicy(ctx, tenantID, namespaceID)

	// Get all accessible namespaces
	allUserRoles, err := s.store.GetUserRoles(ctx, userID, tenantID)
	if err != nil {
		return nil, err
	}

	// Build namespace access list
	namespaceAccessMap := make(map[uuid.UUID]*NamespaceAccess)
	for _, ur := range allUserRoles {
		var nsID uuid.UUID
		if ur.NamespaceID != nil {
			nsID = *ur.NamespaceID
		} else {
			continue // Tenant-wide roles don't have specific namespace
		}

		access, exists := namespaceAccessMap[nsID]
		if !exists {
			// Get namespace details
			ns, err := s.store.GetNamespace(ctx, nsID)
			if err != nil {
				continue
			}
			access = &NamespaceAccess{
				NamespaceID:        ns.ID,
				NamespaceName:      ns.Name,
				NamespaceSlug:      ns.Slug,
				DataClassification: ns.DataClassification,
				Roles:              []string{},
				Permissions:        []string{},
			}
			namespaceAccessMap[nsID] = access
		}

		access.Roles = append(access.Roles, ur.RoleName)
		access.Permissions = append(access.Permissions, ur.RolePermissions...)
	}

	namespaces := make([]NamespaceAccess, 0, len(namespaceAccessMap))
	for _, access := range namespaceAccessMap {
		namespaces = append(namespaces, *access)
	}

	return &EffectivePermissions{
		UserID:      userID,
		TenantID:    tenantID,
		NamespaceID: namespaceID,
		Permissions: permissions,
		Roles:       roles,
		LLMPolicy:   llmPolicy,
		Namespaces:  namespaces,
	}, nil
}

// HasPermission checks if user has a specific permission
func (s *Service) HasPermission(ctx context.Context, userID, tenantID uuid.UUID, namespaceID *uuid.UUID, permission string) (bool, error) {
	perms, err := s.GetEffectivePermissions(ctx, userID, tenantID, namespaceID)
	if err != nil {
		return false, err
	}

	return s.checkPermission(perms.Permissions, permission), nil
}

// checkPermission checks if a permission is granted given a list of permissions
func (s *Service) checkPermission(permissions []string, required string) bool {
	for _, perm := range permissions {
		// Exact match
		if perm == required {
			return true
		}

		// Wildcard match (e.g., "compliance:*" matches "compliance:read")
		if strings.HasSuffix(perm, ":*") {
			prefix := strings.TrimSuffix(perm, "*")
			if strings.HasPrefix(required, prefix) {
				return true
			}
		}

		// Full wildcard (e.g., "compliance:*:read" matches "compliance:privacy:read")
		if strings.Contains(perm, ":*:") {
			parts := strings.Split(perm, ":*:")
			if len(parts) == 2 {
				if strings.HasPrefix(required, parts[0]+":") && strings.HasSuffix(required, ":"+parts[1]) {
					return true
				}
			}
		}

		// "own" namespace handling (e.g., "compliance:own:read" when in own namespace)
		// This requires context about whether the user is in their "own" namespace
		// For simplicity, we'll handle "own" as a prefix match for now
		if strings.Contains(perm, ":own:") {
			ownPerm := strings.Replace(perm, ":own:", ":", 1)
			if ownPerm == required || strings.HasPrefix(required, strings.TrimSuffix(ownPerm, "*")) {
				return true
			}
		}
	}

	return false
}

// HasAnyPermission checks if user has any of the specified permissions
func (s *Service) HasAnyPermission(ctx context.Context, userID, tenantID uuid.UUID, namespaceID *uuid.UUID, permissions []string) (bool, error) {
	perms, err := s.GetEffectivePermissions(ctx, userID, tenantID, namespaceID)
	if err != nil {
		return false, err
	}

	for _, required := range permissions {
		if s.checkPermission(perms.Permissions, required) {
			return true, nil
		}
	}

	return false, nil
}

// HasAllPermissions checks if user has all specified permissions
func (s *Service) HasAllPermissions(ctx context.Context, userID, tenantID uuid.UUID, namespaceID *uuid.UUID, permissions []string) (bool, error) {
	perms, err := s.GetEffectivePermissions(ctx, userID, tenantID, namespaceID)
	if err != nil {
		return false, err
	}

	for _, required := range permissions {
		if !s.checkPermission(perms.Permissions, required) {
			return false, nil
		}
	}

	return true, nil
}

// CanAccessNamespace checks if user can access a namespace
func (s *Service) CanAccessNamespace(ctx context.Context, userID, tenantID, namespaceID uuid.UUID) (bool, error) {
	userRoles, err := s.store.GetUserRolesForNamespace(ctx, userID, tenantID, &namespaceID)
	if err != nil {
		return false, err
	}

	return len(userRoles) > 0, nil
}

// CanAccessDataCategory checks if user can access a data category via LLM
func (s *Service) CanAccessDataCategory(ctx context.Context, userID, tenantID uuid.UUID, namespaceID *uuid.UUID, category string) (bool, string, error) {
	policy, err := s.store.GetEffectiveLLMPolicy(ctx, tenantID, namespaceID)
	if err != nil {
		return false, "", err
	}

	// No policy = allow all
	if policy == nil {
		return true, "", nil
	}

	// Check blocked categories
	for _, blocked := range policy.BlockedDataCategories {
		if blocked == category {
			return false, "data category is blocked by policy", nil
		}
	}

	// If allowed categories are specified, check if category is in the list
	if len(policy.AllowedDataCategories) > 0 {
		allowed := false
		for _, a := range policy.AllowedDataCategories {
			if a == category {
				allowed = true
				break
			}
		}
		if !allowed {
			return false, "data category is not in allowed list", nil
		}
	}

	return true, "", nil
}

// CanUseModel checks if a model is allowed by policy
func (s *Service) CanUseModel(ctx context.Context, tenantID uuid.UUID, namespaceID *uuid.UUID, model string) (bool, error) {
	policy, err := s.store.GetEffectiveLLMPolicy(ctx, tenantID, namespaceID)
	if err != nil {
		return false, err
	}

	// No policy = allow all models
	if policy == nil || len(policy.AllowedModels) == 0 {
		return true, nil
	}

	// Check if model is in allowed list
	for _, allowed := range policy.AllowedModels {
		if allowed == model {
			return true, nil
		}
		// Partial match for model families (e.g., "qwen2.5" matches "qwen2.5:7b")
		if strings.HasPrefix(model, allowed+":") || strings.HasPrefix(model, allowed+"-") {
			return true, nil
		}
	}

	return false, nil
}

// GetPIIRedactionLevel returns the PII redaction level for a namespace
func (s *Service) GetPIIRedactionLevel(ctx context.Context, tenantID uuid.UUID, namespaceID *uuid.UUID) (PIIRedactionLevel, bool, error) {
	policy, err := s.store.GetEffectiveLLMPolicy(ctx, tenantID, namespaceID)
	if err != nil {
		return PIIRedactionStrict, true, err
	}

	// No policy = use strict defaults
	if policy == nil {
		return PIIRedactionStrict, true, nil
	}

	return policy.PIIRedactionLevel, policy.RequirePIIRedaction, nil
}

// GetUserAccessibleNamespaces returns all namespaces a user can access
func (s *Service) GetUserAccessibleNamespaces(ctx context.Context, userID, tenantID uuid.UUID) ([]*Namespace, error) {
	userRoles, err := s.store.GetUserRoles(ctx, userID, tenantID)
	if err != nil {
		return nil, err
	}

	// Get unique namespace IDs
	namespaceIDs := make(map[uuid.UUID]bool)
	hasTenantWideRole := false

	for _, ur := range userRoles {
		if ur.NamespaceID != nil {
			namespaceIDs[*ur.NamespaceID] = true
		} else {
			hasTenantWideRole = true
		}
	}

	// If user has tenant-wide role, return all namespaces
	if hasTenantWideRole {
		return s.store.ListNamespaces(ctx, tenantID)
	}

	// Otherwise, return only specific namespaces
	var namespaces []*Namespace
	for nsID := range namespaceIDs {
		ns, err := s.store.GetNamespace(ctx, nsID)
		if err != nil {
			continue
		}
		namespaces = append(namespaces, ns)
	}

	return namespaces, nil
}

// AssignRoleToUser assigns a role to a user
func (s *Service) AssignRoleToUser(ctx context.Context, ur *UserRole, grantorUserID uuid.UUID) error {
	// Check if grantor has permission to assign roles
	hasPermission, err := s.HasAnyPermission(ctx, grantorUserID, ur.TenantID, ur.NamespaceID, []string{
		"rbac:assign",
		"namespace:own:admin",
		PermissionComplianceAll,
	})
	if err != nil {
		return err
	}
	if !hasPermission {
		return ErrPermissionDenied
	}

	ur.GrantedBy = grantorUserID
	return s.store.AssignRole(ctx, ur)
}

// RevokeRoleFromUser revokes a role from a user
func (s *Service) RevokeRoleFromUser(ctx context.Context, userID, roleID, tenantID uuid.UUID, namespaceID *uuid.UUID, revokerUserID uuid.UUID) error {
	// Check if revoker has permission to revoke roles
	hasPermission, err := s.HasAnyPermission(ctx, revokerUserID, tenantID, namespaceID, []string{
		"rbac:revoke",
		"namespace:own:admin",
		PermissionComplianceAll,
	})
	if err != nil {
		return err
	}
	if !hasPermission {
		return ErrPermissionDenied
	}

	return s.store.RevokeRole(ctx, userID, roleID, tenantID, namespaceID)
}
