package mru

import (
	"context"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

type SearchScopeMetadata interface {
	GetSearchScopeObjects(ctx context.Context) []string
	PackSearchScopeObjectNames(ctx context.Context) []string
}

type SearchScopePermissionChecker interface {
	CheckObjectPermissionWithUser(ctx context.Context, objectAPIName string, operation string, user *models.UserSession) bool
}

type contractSearchScope struct {
	mru         *Service
	metadata    SearchScopeMetadata
	permissions SearchScopePermissionChecker
}

func NewContractSearchScope(mruService *Service, metadata SearchScopeMetadata, permissions SearchScopePermissionChecker) contract.SearchScope {
	if mruService == nil && (metadata == nil || permissions == nil) {
		return nil
	}
	return &contractSearchScope{
		mru:         mruService,
		metadata:    metadata,
		permissions: permissions,
	}
}

func (s *contractSearchScope) ScopeOrder(ctx context.Context, user *models.UserSession) []contract.SearchScopeItem {
	if s == nil || s.mru == nil || user == nil {
		return []contract.SearchScopeItem{}
	}

	current := s.mru.GetScopeOrder(ctx, user.ID)
	if s.metadata != nil && s.permissions != nil {
		defaultObjects := filterSearchScopeObjectsByReadAccess(ctx, user, s.metadata.GetSearchScopeObjects(ctx), s.permissions)
		if len(defaultObjects) > 0 {
			return scopeOrderItemsToContract(s.mru.BuildScopeOrderResponse(ctx, mergeScopeOrderObjects(defaultObjects, current)))
		}
		return []contract.SearchScopeItem{}
	}

	if len(current) <= len(DefaultScopeObjects()) {
		return []contract.SearchScopeItem{}
	}
	return scopeOrderItemsToContract(current)
}

func (s *contractSearchScope) CuratedObjectNames(ctx context.Context, user *models.UserSession) []string {
	if s == nil || s.metadata == nil || s.permissions == nil || user == nil {
		return []string{}
	}
	return filterSearchScopeObjectsByReadAccess(ctx, user, s.metadata.PackSearchScopeObjectNames(ctx), s.permissions)
}

func scopeOrderItemsToContract(items []ScopeOrderItem) []contract.SearchScopeItem {
	if len(items) == 0 {
		return []contract.SearchScopeItem{}
	}
	out := make([]contract.SearchScopeItem, 0, len(items))
	for _, item := range items {
		out = append(out, contract.SearchScopeItem{
			ObjectAPIName: item.Type,
			Name:          item.Name,
			Label:         item.Label,
			KeyPrefix:     item.KeyPrefix,
		})
	}
	return out
}

func filterSearchScopeObjectsByReadAccess(ctx context.Context, user *models.UserSession, objectAPINames []string, permissions SearchScopePermissionChecker) []string {
	if user == nil || permissions == nil || len(objectAPINames) == 0 {
		return nil
	}

	filtered := make([]string, 0, len(objectAPINames))
	seen := make(map[string]struct{}, len(objectAPINames))
	for _, objectAPIName := range objectAPINames {
		objectAPIName = strings.TrimSpace(objectAPIName)
		if objectAPIName == "" {
			continue
		}
		key := strings.ToLower(objectAPIName)
		if _, dup := seen[key]; dup {
			continue
		}
		if !permissions.CheckObjectPermissionWithUser(ctx, objectAPIName, constants.PermRead, user) {
			continue
		}
		seen[key] = struct{}{}
		filtered = append(filtered, objectAPIName)
	}
	return filtered
}

func mergeScopeOrderObjects(defaultObjects []string, current []ScopeOrderItem) []string {
	if len(defaultObjects) == 0 {
		return nil
	}

	allowed := make(map[string]string, len(defaultObjects))
	for _, objectAPIName := range defaultObjects {
		key := strings.ToLower(strings.TrimSpace(objectAPIName))
		if key == "" {
			continue
		}
		allowed[key] = objectAPIName
	}

	merged := make([]string, 0, len(defaultObjects))
	seen := make(map[string]struct{}, len(defaultObjects))
	for _, item := range current {
		key := strings.ToLower(strings.TrimSpace(item.Type))
		if key == "" {
			key = strings.ToLower(strings.TrimSpace(item.Name))
		}
		canonical, ok := allowed[key]
		if !ok {
			continue
		}
		if _, dup := seen[key]; dup {
			continue
		}
		seen[key] = struct{}{}
		merged = append(merged, canonical)
	}

	for _, objectAPIName := range defaultObjects {
		key := strings.ToLower(strings.TrimSpace(objectAPIName))
		if key == "" {
			continue
		}
		if _, dup := seen[key]; dup {
			continue
		}
		seen[key] = struct{}{}
		merged = append(merged, objectAPIName)
	}

	return merged
}
