package mru

import (
	"context"
	"strconv"

	"github.com/nexuscrm/backend/internal/domain/ports"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// Service handles MRU (Most Recently Used) tracking for search scope and relevant items
type Service struct {
	repo ports.MRURepository
}

// NewService creates a new MRU service
func NewService(repo ports.MRURepository) *Service {
	return &Service{
		repo: repo,
	}
}

// ScopeOrderItem represents an item in the search scope order (Salesforce v63.0 compatible)
type ScopeOrderItem struct {
	Type      string `json:"type"`
	URL       string `json:"url"`
	Name      string `json:"name"`
	Label     string `json:"label"`
	KeyPrefix string `json:"keyPrefix"`
}

// SFAttributes represents the Salesforce standard attributes for a record (v63.0 compatible)
type SFAttributes struct {
	Type string `json:"type"`
	URL  string `json:"url"`
}

// RelevantItem represents a single relevant item in Salesforce v63.0 format
// This is a flat record with an attributes wrapper, not grouped by object
type RelevantItem struct {
	Attributes SFAttributes `json:"attributes"`
	Id         string       `json:"Id"`
	Name       string       `json:"Name"`
}

// DefaultScopeObjects returns the default search scope objects.
// ARCHITECTURE NOTE: This is a fallback for when metadata is unavailable.
// Prefer calling MetadataService.GetSearchableObjects() for dynamic scope.
// Only core system objects are listed here as ultimate fallback.
func DefaultScopeObjects() []string {
	return []string{
		constants.TableUser, // Core system table - always present
	}
}

// TrackObjectInteraction tracks when a user interacts with an object type
// This is called when searching, viewing lists, etc.
func (s *Service) TrackObjectInteraction(ctx context.Context, userID, objectAPIName string) {
	if userID == "" || objectAPIName == "" {
		return
	}

	if err := s.repo.TrackObjectInteraction(ctx, userID, objectAPIName); err != nil {
		log.Warn().Err(err).Str("user", userID).Str("object", objectAPIName).Msg("Failed to track object interaction")
	}
}

// TrackRecordAccess tracks when a user accesses a specific record
// This is called when viewing, editing, or creating a record
func (s *Service) TrackRecordAccess(ctx context.Context, userID, objectAPIName, recordID string) {
	if userID == "" || objectAPIName == "" || recordID == "" {
		return
	}

	// Track both record access and object interaction
	if err := s.repo.TrackRecentItem(ctx, userID, objectAPIName, recordID); err != nil {
		log.Warn().Err(err).Str("user", userID).Str("object", objectAPIName).Str("record", recordID).Msg("Failed to track recent item")
	}

	// Also increment object interaction count
	s.TrackObjectInteraction(ctx, userID, objectAPIName)
}

// GetScopeOrder returns the user's search scope order (Salesforce v63.0 compatible response)
func (s *Service) GetScopeOrder(ctx context.Context, userID, apiVersion string) []ScopeOrderItem {
	if apiVersion == "" {
		apiVersion = "v63.0"
	}

	// Get user's scope order from database
	interactions, err := s.repo.GetScopeOrder(ctx, userID)
	if err != nil {
		log.Warn().Err(err).Str("user", userID).Msg("Failed to get scope order, using defaults")
		return s.buildScopeOrderResponse(ctx, DefaultScopeObjects())
	}

	if len(interactions) == 0 {
		return s.buildScopeOrderResponse(ctx, DefaultScopeObjects())
	}

	// Build ordered list
	objects := make([]string, len(interactions))
	for i, oi := range interactions {
		objects[i] = oi.ObjectAPIName
	}

	return s.buildScopeOrderResponse(ctx, objects)
}

func (s *Service) buildScopeOrderResponse(ctx context.Context, objects []string) []ScopeOrderItem {
	// Batch fetch metadata for all objects
	metadataMap, err := s.repo.GetObjectMetaBatch(ctx, objects)
	if err != nil {
		log.Warn().Err(err).Msg("Failed to batch fetch object metadata for scope order")
		metadataMap = make(map[string]*models.ObjectMetadata)
	}

	result := make([]ScopeOrderItem, len(objects))
	for i, obj := range objects {
		label := obj // Default label is the API name
		if meta, ok := metadataMap[obj]; ok && meta != nil {
			label = meta.Label
		}

		result[i] = ScopeOrderItem{
			Type:      obj,
			URL:       "/services/data/v63.0/sobjects/" + obj + "/describe",
			Name:      obj,
			Label:     label,
			KeyPrefix: GetKeyPrefix(obj),
		}
	}
	return result
}

// SetObjectPinned pins or unpins an object for a user
func (s *Service) SetObjectPinned(ctx context.Context, userID, objectAPIName string, pinned bool) error {
	return s.repo.SetObjectPinned(ctx, userID, objectAPIName, pinned)
}

// GetRelevantItems returns the user's recently accessed records (Salesforce v63.0 compatible response)
// Returns a flat array of records with attributes wrapper, matching Salesforce /sobjects/relevantItems
func (s *Service) GetRelevantItems(ctx context.Context, userID string, sobjects []string, lastUpdatedID string, apiVersion string) ([]RelevantItem, string, error) {
	if apiVersion == "" {
		apiVersion = "v63.0"
	}

	// Parse lastUpdatedId
	var lastUpdatedInt int64
	if lastUpdatedID != "" {
		if parsed, err := strconv.ParseInt(lastUpdatedID, 10, 64); err == nil {
			lastUpdatedInt = parsed
		}
	}

	// Get recent items from database
	items, maxCounter, err := s.repo.GetRelevantItems(ctx, userID, sobjects, lastUpdatedInt)
	if err != nil {
		return nil, "", err
	}

	if len(items) == 0 {
		return []RelevantItem{}, strconv.FormatInt(maxCounter, 10), nil
	}

	// Group record IDs by object for batch Name lookup
	byObject := make(map[string][]string)
	for _, item := range items {
		byObject[item.ObjectAPIName] = append(byObject[item.ObjectAPIName], item.RecordID)
	}

	// Batch fetch record names (map of recordID -> Name)
	recordNames, err := s.repo.GetRecordNamesBatch(ctx, byObject)
	if err != nil {
		log.Warn().Err(err).Msg("Failed to batch fetch record names, using IDs as names")
		recordNames = make(map[string]string)
	}

	// Build flat response array (Salesforce v63.0 format)
	result := make([]RelevantItem, 0, len(items))
	for _, item := range items {
		name := item.RecordID // Default to ID if name not found
		if n, ok := recordNames[item.RecordID]; ok && n != "" {
			name = n
		}

		result = append(result, RelevantItem{
			Attributes: SFAttributes{
				Type: item.ObjectAPIName,
				URL:  "/services/data/" + apiVersion + "/sobjects/" + item.ObjectAPIName + "/" + item.RecordID,
			},
			Id:   item.RecordID,
			Name: name,
		})
	}

	return result, strconv.FormatInt(maxCounter, 10), nil
}

// Note: GetKeyPrefix is now defined in id_registry.go as the SSoT for keyPrefix lookup
