package service import ( "encoding/json" "errors" "accounting-app/internal/repository" ) // User preference service errors var ( ErrPreferenceNotFound = errors.New("user preference not found") ) // UserPreferenceOutput represents the output format for user preferences type UserPreferenceOutput struct { LastAccountID *uint `json:"last_account_id,omitempty"` LastCategoryID *uint `json:"last_category_id,omitempty"` FrequentAccounts []uint `json:"frequent_accounts,omitempty"` FrequentCategories []uint `json:"frequent_categories,omitempty"` } // UserPreferenceService handles business logic for user preferences type UserPreferenceService struct { prefRepo *repository.UserPreferenceRepository accountRepo *repository.AccountRepository categoryRepo *repository.CategoryRepository } // NewUserPreferenceService creates a new UserPreferenceService instance func NewUserPreferenceService( prefRepo *repository.UserPreferenceRepository, accountRepo *repository.AccountRepository, categoryRepo *repository.CategoryRepository, ) *UserPreferenceService { return &UserPreferenceService{ prefRepo: prefRepo, accountRepo: accountRepo, categoryRepo: categoryRepo, } } // GetPreferences retrieves user preferences func (s *UserPreferenceService) GetPreferences(userID uint) (*UserPreferenceOutput, error) { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return nil, err } output := &UserPreferenceOutput{ LastAccountID: pref.LastAccountID, LastCategoryID: pref.LastCategoryID, } // Parse frequent accounts JSON if pref.FrequentAccounts != "" { var accounts []uint if err := json.Unmarshal([]byte(pref.FrequentAccounts), &accounts); err == nil { output.FrequentAccounts = accounts } } // Parse frequent categories JSON if pref.FrequentCategories != "" { var categories []uint if err := json.Unmarshal([]byte(pref.FrequentCategories), &categories); err == nil { output.FrequentCategories = categories } } return output, nil } // RecordAccountUsage records that an account was used and updates preferences func (s *UserPreferenceService) RecordAccountUsage(userID uint, accountID uint) error { // Verify account exists exists, err := s.accountRepo.ExistsByID(userID, accountID) if err != nil { return err } if !exists { return errors.New("account not found") } // Update last account if err := s.prefRepo.UpdateLastAccount(userID, accountID); err != nil { return err } // Update frequent accounts return s.updateFrequentAccounts(userID, accountID) } // RecordCategoryUsage records that a category was used and updates preferences func (s *UserPreferenceService) RecordCategoryUsage(userID uint, categoryID uint) error { // Verify category exists exists, err := s.categoryRepo.ExistsByID(userID, categoryID) if err != nil { return err } if !exists { return errors.New("category not found") } // Update last category if err := s.prefRepo.UpdateLastCategory(userID, categoryID); err != nil { return err } // Update frequent categories return s.updateFrequentCategories(userID, categoryID) } // RecordTransactionUsage records both account and category usage from a transaction func (s *UserPreferenceService) RecordTransactionUsage(userID uint, accountID, categoryID uint) error { if err := s.RecordAccountUsage(userID, accountID); err != nil { return err } return s.RecordCategoryUsage(userID, categoryID) } // updateFrequentAccounts updates the frequent accounts list func (s *UserPreferenceService) updateFrequentAccounts(userID uint, accountID uint) error { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return err } var accounts []uint if pref.FrequentAccounts != "" { if err := json.Unmarshal([]byte(pref.FrequentAccounts), &accounts); err != nil { accounts = []uint{} } } // Move the used account to the front (most recent) accounts = moveToFront(accounts, accountID) // Keep only top 10 frequent accounts if len(accounts) > 10 { accounts = accounts[:10] } // Save back jsonBytes, err := json.Marshal(accounts) if err != nil { return err } return s.prefRepo.UpdateFrequentAccounts(userID, string(jsonBytes)) } // updateFrequentCategories updates the frequent categories list func (s *UserPreferenceService) updateFrequentCategories(userID uint, categoryID uint) error { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return err } var categories []uint if pref.FrequentCategories != "" { if err := json.Unmarshal([]byte(pref.FrequentCategories), &categories); err != nil { categories = []uint{} } } // Move the used category to the front (most recent) categories = moveToFront(categories, categoryID) // Keep only top 10 frequent categories if len(categories) > 10 { categories = categories[:10] } // Save back jsonBytes, err := json.Marshal(categories) if err != nil { return err } return s.prefRepo.UpdateFrequentCategories(userID, string(jsonBytes)) } // GetLastUsedAccount returns the last used account ID func (s *UserPreferenceService) GetLastUsedAccount(userID uint) (*uint, error) { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return nil, err } return pref.LastAccountID, nil } // GetLastUsedCategory returns the last used category ID func (s *UserPreferenceService) GetLastUsedCategory(userID uint) (*uint, error) { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return nil, err } return pref.LastCategoryID, nil } // GetFrequentAccounts returns the list of frequently used account IDs func (s *UserPreferenceService) GetFrequentAccounts(userID uint) ([]uint, error) { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return nil, err } if pref.FrequentAccounts == "" { return []uint{}, nil } var accounts []uint if err := json.Unmarshal([]byte(pref.FrequentAccounts), &accounts); err != nil { return []uint{}, nil } return accounts, nil } // GetFrequentCategories returns the list of frequently used category IDs func (s *UserPreferenceService) GetFrequentCategories(userID uint) ([]uint, error) { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return nil, err } if pref.FrequentCategories == "" { return []uint{}, nil } var categories []uint if err := json.Unmarshal([]byte(pref.FrequentCategories), &categories); err != nil { return []uint{}, nil } return categories, nil } // ClearPreferences clears all user preferences func (s *UserPreferenceService) ClearPreferences(userID uint) error { pref, err := s.prefRepo.GetOrCreate(userID) if err != nil { return err } pref.LastAccountID = nil pref.LastCategoryID = nil pref.FrequentAccounts = "" pref.FrequentCategories = "" return s.prefRepo.Update(pref) } // moveToFront moves an ID to the front of the slice, removing duplicates func moveToFront(ids []uint, id uint) []uint { // Remove existing occurrence result := make([]uint, 0, len(ids)+1) result = append(result, id) for _, existingID := range ids { if existingID != id { result = append(result, existingID) } } return result }