package repositories import ( "errors" "fmt" "time" "attune-heart-therapy/internal/models" "gorm.io/gorm" ) // bookingRepository implements the BookingRepository interface type bookingRepository struct { db *gorm.DB } // NewBookingRepository creates a new instance of BookingRepository func NewBookingRepository(db *gorm.DB) BookingRepository { return &bookingRepository{ db: db, } } // Create creates a new booking in the database func (r *bookingRepository) Create(booking *models.Booking) error { if booking == nil { return errors.New("booking cannot be nil") } if err := r.db.Create(booking).Error; err != nil { return fmt.Errorf("failed to create booking: %w", err) } return nil } // GetByID retrieves a booking by its ID with user preloaded func (r *bookingRepository) GetByID(id uint) (*models.Booking, error) { if id == 0 { return nil, errors.New("invalid booking ID") } var booking models.Booking if err := r.db.Preload("User").First(&booking, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("booking with ID %d not found", id) } return nil, fmt.Errorf("failed to get booking by ID: %w", err) } return &booking, nil } // GetByUserID retrieves all bookings for a specific user with user preloaded func (r *bookingRepository) GetByUserID(userID uint) ([]models.Booking, error) { if userID == 0 { return nil, errors.New("invalid user ID") } var bookings []models.Booking if err := r.db.Preload("User").Where("user_id = ?", userID). Order("scheduled_at DESC").Find(&bookings).Error; err != nil { return nil, fmt.Errorf("failed to get bookings for user %d: %w", userID, err) } return bookings, nil } // Update updates an existing booking in the database func (r *bookingRepository) Update(booking *models.Booking) error { if booking == nil { return errors.New("booking cannot be nil") } if booking.ID == 0 { return errors.New("booking ID is required for update") } // Check if booking exists var existingBooking models.Booking if err := r.db.First(&existingBooking, booking.ID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("booking with ID %d not found", booking.ID) } return fmt.Errorf("failed to check booking existence: %w", err) } // Update the booking if err := r.db.Save(booking).Error; err != nil { return fmt.Errorf("failed to update booking: %w", err) } return nil } // Delete soft deletes a booking by its ID func (r *bookingRepository) Delete(id uint) error { if id == 0 { return errors.New("invalid booking ID") } // Check if booking exists var booking models.Booking if err := r.db.First(&booking, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("booking with ID %d not found", id) } return fmt.Errorf("failed to check booking existence: %w", err) } // Soft delete the booking if err := r.db.Delete(&booking).Error; err != nil { return fmt.Errorf("failed to delete booking: %w", err) } return nil } // GetUpcomingBookings retrieves all upcoming bookings for notification scheduling func (r *bookingRepository) GetUpcomingBookings() ([]models.Booking, error) { var bookings []models.Booking // Get bookings that are scheduled and in the future if err := r.db.Preload("User"). Where("status = ? AND scheduled_at > ?", models.BookingStatusScheduled, time.Now()). Order("scheduled_at ASC"). Find(&bookings).Error; err != nil { return nil, fmt.Errorf("failed to get upcoming bookings: %w", err) } return bookings, nil } // GetByPaymentID retrieves a booking by its payment ID with user preloaded func (r *bookingRepository) GetByPaymentID(paymentID string) (*models.Booking, error) { if paymentID == "" { return nil, errors.New("payment ID cannot be empty") } var booking models.Booking if err := r.db.Preload("User").Where("payment_id = ?", paymentID).First(&booking).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("booking with payment ID %s not found", paymentID) } return nil, fmt.Errorf("failed to get booking by payment ID: %w", err) } return &booking, nil } // GetAllBookings retrieves all bookings with pagination func (r *bookingRepository) GetAllBookings(limit, offset int) ([]models.Booking, int64, error) { var bookings []models.Booking var total int64 // Get total count if err := r.db.Model(&models.Booking{}).Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("failed to get bookings count: %w", err) } // Get bookings with pagination and user preloaded if err := r.db.Preload("User").Limit(limit).Offset(offset). Order("created_at DESC").Find(&bookings).Error; err != nil { return nil, 0, fmt.Errorf("failed to get bookings: %w", err) } // Clear password hashes for security for i := range bookings { bookings[i].User.PasswordHash = "" } return bookings, total, nil } // GetBookingStats retrieves booking statistics for admin dashboard func (r *bookingRepository) GetBookingStats() (*BookingStats, error) { var stats BookingStats // Get total bookings if err := r.db.Model(&models.Booking{}).Count(&stats.TotalBookings).Error; err != nil { return nil, fmt.Errorf("failed to get total bookings count: %w", err) } // Get upcoming bookings if err := r.db.Model(&models.Booking{}). Where("status = ? AND scheduled_at > ?", models.BookingStatusScheduled, time.Now()). Count(&stats.UpcomingBookings).Error; err != nil { return nil, fmt.Errorf("failed to get upcoming bookings count: %w", err) } // Get completed bookings if err := r.db.Model(&models.Booking{}). Where("status = ?", models.BookingStatusCompleted). Count(&stats.CompletedBookings).Error; err != nil { return nil, fmt.Errorf("failed to get completed bookings count: %w", err) } // Get cancelled bookings if err := r.db.Model(&models.Booking{}). Where("status = ?", models.BookingStatusCancelled). Count(&stats.CancelledBookings).Error; err != nil { return nil, fmt.Errorf("failed to get cancelled bookings count: %w", err) } return &stats, nil } // GetFinancialStats retrieves financial statistics for admin reports func (r *bookingRepository) GetFinancialStats(startDate, endDate time.Time) (*FinancialStats, error) { var stats FinancialStats // Get total revenue and booking count for the date range var result struct { TotalRevenue float64 `json:"total_revenue"` TotalBookings int64 `json:"total_bookings"` } if err := r.db.Model(&models.Booking{}). Select("COALESCE(SUM(amount), 0) as total_revenue, COUNT(*) as total_bookings"). Where("payment_status = ? AND created_at >= ? AND created_at <= ?", models.PaymentStatusSucceeded, startDate, endDate). Scan(&result).Error; err != nil { return nil, fmt.Errorf("failed to get financial stats: %w", err) } stats.TotalRevenue = result.TotalRevenue stats.TotalBookings = result.TotalBookings // Calculate average booking value if stats.TotalBookings > 0 { stats.AverageBooking = stats.TotalRevenue / float64(stats.TotalBookings) } return &stats, nil }