package customer import ( "context" "errors" "time" "tm/pkg/logger" mongopkg "tm/pkg/mongo" "github.com/google/uuid" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // Repository defines the interface for customer data operations type Repository interface { Create(ctx context.Context, customer *Customer) error GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) GetByEmail(ctx context.Context, email string) (*Customer, error) GetByUsername(ctx context.Context, username string) (*Customer, error) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) Update(ctx context.Context, customer *Customer) error Delete(ctx context.Context, id uuid.UUID) error List(ctx context.Context, limit, offset int) ([]*Customer, error) Search(ctx context.Context, search string, customerType *string, status *string, companyID *uuid.UUID, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) CountByCompanyID(ctx context.Context, companyID uuid.UUID) (int64, error) CountByType(ctx context.Context, customerType CustomerType) (int64, error) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) error } // customerRepository implements the Repository interface type customerRepository struct { collection *mongo.Collection logger logger.Logger } // NewCustomerRepository creates a new customer repository func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository { collection := mongoManager.GetCollection("customers") // Create indexes repo := &customerRepository{ collection: collection, logger: logger, } repo.createIndexes() return repo } // createIndexes creates necessary database indexes func (r *customerRepository) createIndexes() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Create indexes indexes := []mongo.IndexModel{ { Keys: bson.D{ {Key: "email", Value: 1}, }, Options: options.Index().SetUnique(true), }, { Keys: bson.D{ {Key: "username", Value: 1}, }, Options: options.Index().SetUnique(true), }, { Keys: bson.D{ {Key: "company_name", Value: 1}, }, Options: options.Index().SetSparse(true), }, { Keys: bson.D{ {Key: "registration_number", Value: 1}, }, Options: options.Index().SetSparse(true), }, { Keys: bson.D{ {Key: "tax_id", Value: 1}, }, Options: options.Index().SetSparse(true), }, { Keys: bson.D{ {Key: "company_id", Value: 1}, }, Options: options.Index().SetSparse(true), }, { Keys: bson.D{ {Key: "type", Value: 1}, }, }, { Keys: bson.D{ {Key: "status", Value: 1}, }, }, { Keys: bson.D{ {Key: "industry", Value: 1}, }, }, { Keys: bson.D{ {Key: "created_at", Value: -1}, }, }, { Keys: bson.D{ {Key: "updated_at", Value: -1}, }, }, } _, err := r.collection.Indexes().CreateMany(ctx, indexes) if err != nil { r.logger.Error("Failed to create customer indexes", map[string]interface{}{ "error": err.Error(), }) } else { r.logger.Info("Customer indexes created successfully", map[string]interface{}{}) } } // Create creates a new customer func (r *customerRepository) Create(ctx context.Context, customer *Customer) error { // Set timestamps now := time.Now().Unix() customer.CreatedAt = now customer.UpdatedAt = now // Set defaults if not provided if customer.Language == "" { customer.Language = "en" } if customer.Currency == "" { customer.Currency = "USD" } if customer.Timezone == "" { customer.Timezone = "UTC" } _, err := r.collection.InsertOne(ctx, customer) if err != nil { if mongo.IsDuplicateKeyError(err) { return errors.New("customer with this email already exists") } return err } r.logger.Info("Customer created successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "type": customer.Type, }) return nil } // GetByID retrieves a customer by ID func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) { var customer Customer err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&customer) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("customer not found") } return nil, err } return &customer, nil } // GetByEmail retrieves a customer by email func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) { var customer Customer err := r.collection.FindOne(ctx, bson.M{"email": email}).Decode(&customer) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("customer not found") } return nil, err } return &customer, nil } // GetByUsername retrieves a customer by username func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) { var customer Customer err := r.collection.FindOne(ctx, bson.M{"username": username}).Decode(&customer) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("customer not found") } return nil, err } return &customer, nil } // GetByCompanyName retrieves a customer by company name func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) { var customer Customer err := r.collection.FindOne(ctx, bson.M{"company_name": companyName}).Decode(&customer) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("customer not found") } return nil, err } return &customer, nil } // GetByRegistrationNumber retrieves a customer by registration number func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) { var customer Customer err := r.collection.FindOne(ctx, bson.M{"registration_number": registrationNumber}).Decode(&customer) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("customer not found") } return nil, err } return &customer, nil } // GetByTaxID retrieves a customer by tax ID func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) { var customer Customer err := r.collection.FindOne(ctx, bson.M{"tax_id": taxID}).Decode(&customer) if err != nil { if err == mongo.ErrNoDocuments { return nil, errors.New("customer not found") } return nil, err } return &customer, nil } // GetByCompanyID retrieves customers by company ID with pagination func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) { filter := bson.M{"company_id": companyID} opts := options.Find(). SetLimit(int64(limit)). SetSkip(int64(offset)). SetSort(bson.D{{Key: "created_at", Value: -1}}) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var customers []*Customer if err = cursor.All(ctx, &customers); err != nil { return nil, err } return customers, nil } // Update updates a customer func (r *customerRepository) Update(ctx context.Context, customer *Customer) error { customer.UpdatedAt = time.Now().Unix() filter := bson.M{"_id": customer.ID} update := bson.M{"$set": customer} result, err := r.collection.UpdateOne(ctx, filter, update) if err != nil { return err } if result.MatchedCount == 0 { return errors.New("customer not found") } r.logger.Info("Customer updated successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, }) return nil } // Delete deletes a customer (soft delete by setting status to inactive) func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error { filter := bson.M{"_id": id} update := bson.M{ "$set": bson.M{ "status": CustomerStatusInactive, "updated_at": time.Now().Unix(), }, } result, err := r.collection.UpdateOne(ctx, filter, update) if err != nil { return err } if result.MatchedCount == 0 { return errors.New("customer not found") } r.logger.Info("Customer deleted successfully", map[string]interface{}{ "customer_id": id.String(), }) return nil } // List retrieves customers with pagination func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) { opts := options.Find(). SetLimit(int64(limit)). SetSkip(int64(offset)). SetSort(bson.D{{Key: "created_at", Value: -1}}) cursor, err := r.collection.Find(ctx, bson.M{}, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var customers []*Customer if err = cursor.All(ctx, &customers); err != nil { return nil, err } return customers, nil } // Search searches customers with filters func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *uuid.UUID, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) { filter := bson.M{} // Add search filter if search != "" { filter["$or"] = []bson.M{ {"email": primitive.Regex{Pattern: search, Options: "i"}}, {"company_name": primitive.Regex{Pattern: search, Options: "i"}}, {"first_name": primitive.Regex{Pattern: search, Options: "i"}}, {"last_name": primitive.Regex{Pattern: search, Options: "i"}}, {"full_name": primitive.Regex{Pattern: search, Options: "i"}}, } } // Add type filter if customerType != nil { filter["type"] = *customerType } // Add status filter if status != nil { filter["status"] = *status } // Add company ID filter if companyID != nil { filter["company_id"] = *companyID } // Add industry filter if industry != nil { filter["industry"] = *industry } // Add verification filter if isVerified != nil { filter["is_verified"] = *isVerified } // Add compliance filter if isCompliant != nil { filter["is_compliant"] = *isCompliant } // Add language filter if language != nil { filter["language"] = *language } // Add currency filter if currency != nil { filter["currency"] = *currency } // Set sort options sortDirection := 1 if sortOrder == "desc" { sortDirection = -1 } var sortField string switch sortBy { case "email": sortField = "email" case "company_name": sortField = "company_name" case "updated_at": sortField = "updated_at" case "status": sortField = "status" default: sortField = "created_at" } opts := options.Find(). SetLimit(int64(limit)). SetSkip(int64(offset)). SetSort(bson.D{{Key: sortField, Value: sortDirection}}) cursor, err := r.collection.Find(ctx, filter, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var customers []*Customer if err = cursor.All(ctx, &customers); err != nil { return nil, err } return customers, nil } // CountByCompanyID counts customers by company ID func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID uuid.UUID) (int64, error) { filter := bson.M{"company_id": companyID} count, err := r.collection.CountDocuments(ctx, filter) if err != nil { return 0, err } return count, nil } // CountByType counts customers by type func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) { filter := bson.M{"type": customerType} count, err := r.collection.CountDocuments(ctx, filter) if err != nil { return 0, err } return count, nil } // CountByStatus counts customers by status func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) { filter := bson.M{"status": status} count, err := r.collection.CountDocuments(ctx, filter) if err != nil { return 0, err } return count, nil } // UpdateStatus updates customer status func (r *customerRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error { filter := bson.M{"_id": id} update := bson.M{ "$set": bson.M{ "status": status, "updated_at": time.Now().Unix(), }, } result, err := r.collection.UpdateOne(ctx, filter, update) if err != nil { return err } if result.MatchedCount == 0 { return errors.New("customer not found") } return nil } // UpdateVerification updates customer verification status func (r *customerRepository) UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) error { filter := bson.M{"_id": id} update := bson.M{ "$set": bson.M{ "is_verified": isVerified, "is_compliant": isCompliant, "updated_at": time.Now().Unix(), }, } if complianceNotes != nil { update["$set"].(bson.M)["compliance_notes"] = *complianceNotes } result, err := r.collection.UpdateOne(ctx, filter, update) if err != nil { return err } if result.MatchedCount == 0 { return errors.New("customer not found") } return nil }