package services import ( "fmt" "log" "time" "attune-heart-therapy/internal/models" "attune-heart-therapy/internal/repositories" ) // adminService implements the AdminService interface type adminService struct { userRepo repositories.UserRepository bookingRepo repositories.BookingRepository scheduleRepo repositories.ScheduleRepository } // NewAdminService creates a new instance of AdminService func NewAdminService( userRepo repositories.UserRepository, bookingRepo repositories.BookingRepository, scheduleRepo repositories.ScheduleRepository, ) AdminService { return &adminService{ userRepo: userRepo, bookingRepo: bookingRepo, scheduleRepo: scheduleRepo, } } // GetDashboardStats retrieves dashboard statistics for admin overview func (s *adminService) GetDashboardStats() (*DashboardStats, error) { var stats DashboardStats // Get user statistics totalUsers, err := s.userRepo.GetActiveUsersCount() if err != nil { log.Printf("Failed to get total users count: %v", err) return nil, fmt.Errorf("failed to get user statistics: %w", err) } stats.TotalUsers = totalUsers stats.ActiveUsers = totalUsers // For now, all users are considered active // Get booking statistics bookingStats, err := s.bookingRepo.GetBookingStats() if err != nil { log.Printf("Failed to get booking statistics: %v", err) return nil, fmt.Errorf("failed to get booking statistics: %w", err) } stats.TotalBookings = bookingStats.TotalBookings stats.UpcomingBookings = bookingStats.UpcomingBookings stats.CompletedBookings = bookingStats.CompletedBookings stats.CancelledBookings = bookingStats.CancelledBookings // Get financial statistics (all time) allTimeStart := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) allTimeEnd := time.Now() financialStats, err := s.bookingRepo.GetFinancialStats(allTimeStart, allTimeEnd) if err != nil { log.Printf("Failed to get financial statistics: %v", err) return nil, fmt.Errorf("failed to get financial statistics: %w", err) } stats.TotalRevenue = financialStats.TotalRevenue // Get monthly revenue (current month) now := time.Now() monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) monthEnd := monthStart.AddDate(0, 1, 0) monthlyStats, err := s.bookingRepo.GetFinancialStats(monthStart, monthEnd) if err != nil { log.Printf("Failed to get monthly financial statistics: %v", err) return nil, fmt.Errorf("failed to get monthly financial statistics: %w", err) } stats.MonthlyRevenue = monthlyStats.TotalRevenue return &stats, nil } // GetFinancialReports generates financial reports for a given date range func (s *adminService) GetFinancialReports(startDate, endDate time.Time) (*FinancialReport, error) { // Validate date range if endDate.Before(startDate) { return nil, fmt.Errorf("end date cannot be before start date") } // Get overall financial statistics financialStats, err := s.bookingRepo.GetFinancialStats(startDate, endDate) if err != nil { log.Printf("Failed to get financial statistics for date range %v to %v: %v", startDate, endDate, err) return nil, fmt.Errorf("failed to get financial statistics: %w", err) } report := &FinancialReport{ StartDate: startDate, EndDate: endDate, TotalRevenue: financialStats.TotalRevenue, TotalBookings: financialStats.TotalBookings, AverageBooking: financialStats.AverageBooking, } // Generate daily breakdown dailyBreakdown, err := s.generateDailyBreakdown(startDate, endDate) if err != nil { log.Printf("Failed to generate daily breakdown: %v", err) return nil, fmt.Errorf("failed to generate daily breakdown: %w", err) } report.DailyBreakdown = dailyBreakdown // Generate monthly breakdown monthlyBreakdown, err := s.generateMonthlyBreakdown(startDate, endDate) if err != nil { log.Printf("Failed to generate monthly breakdown: %v", err) return nil, fmt.Errorf("failed to generate monthly breakdown: %w", err) } report.MonthlyBreakdown = monthlyBreakdown return report, nil } // CreateSchedule creates a new schedule slot func (s *adminService) CreateSchedule(req CreateScheduleRequest) (*models.Schedule, error) { // Validate the request if req.EndTime.Before(req.StartTime) || req.EndTime.Equal(req.StartTime) { return nil, fmt.Errorf("end time must be after start time") } if req.StartTime.Before(time.Now()) { return nil, fmt.Errorf("cannot create schedule slots in the past") } if req.MaxBookings < 1 { return nil, fmt.Errorf("max bookings must be at least 1") } // Create the schedule schedule := &models.Schedule{ StartTime: req.StartTime, EndTime: req.EndTime, MaxBookings: req.MaxBookings, IsAvailable: true, BookedCount: 0, } if err := s.scheduleRepo.Create(schedule); err != nil { log.Printf("Failed to create schedule: %v", err) return nil, fmt.Errorf("failed to create schedule: %w", err) } log.Printf("Successfully created schedule slot from %v to %v with max bookings %d", schedule.StartTime, schedule.EndTime, schedule.MaxBookings) return schedule, nil } // UpdateSchedule updates an existing schedule slot func (s *adminService) UpdateSchedule(scheduleID uint, req UpdateScheduleRequest) (*models.Schedule, error) { // Get the existing schedule schedule, err := s.scheduleRepo.GetByID(scheduleID) if err != nil { log.Printf("Failed to get schedule %d: %v", scheduleID, err) return nil, fmt.Errorf("schedule not found: %w", err) } // Update fields if provided if req.StartTime != nil { schedule.StartTime = *req.StartTime } if req.EndTime != nil { schedule.EndTime = *req.EndTime } if req.MaxBookings != nil { if *req.MaxBookings < 1 { return nil, fmt.Errorf("max bookings must be at least 1") } if *req.MaxBookings < schedule.BookedCount { return nil, fmt.Errorf("max bookings cannot be less than current booked count (%d)", schedule.BookedCount) } schedule.MaxBookings = *req.MaxBookings } if req.IsAvailable != nil { schedule.IsAvailable = *req.IsAvailable } // Validate time range if updated if !schedule.EndTime.After(schedule.StartTime) { return nil, fmt.Errorf("end time must be after start time") } // Update the schedule if err := s.scheduleRepo.Update(schedule); err != nil { log.Printf("Failed to update schedule %d: %v", scheduleID, err) return nil, fmt.Errorf("failed to update schedule: %w", err) } log.Printf("Successfully updated schedule %d", scheduleID) return schedule, nil } // GetAllUsers retrieves all users with pagination func (s *adminService) GetAllUsers(limit, offset int) ([]models.User, int64, error) { if limit <= 0 { limit = 50 // Default limit } if limit > 100 { limit = 100 // Maximum limit } if offset < 0 { offset = 0 } users, total, err := s.userRepo.GetAllUsers(limit, offset) if err != nil { log.Printf("Failed to get users with limit %d, offset %d: %v", limit, offset, err) return nil, 0, fmt.Errorf("failed to get users: %w", err) } return users, total, nil } // GetAllBookings retrieves all bookings with pagination func (s *adminService) GetAllBookings(limit, offset int) ([]models.Booking, int64, error) { if limit <= 0 { limit = 50 // Default limit } if limit > 100 { limit = 100 // Maximum limit } if offset < 0 { offset = 0 } bookings, total, err := s.bookingRepo.GetAllBookings(limit, offset) if err != nil { log.Printf("Failed to get bookings with limit %d, offset %d: %v", limit, offset, err) return nil, 0, fmt.Errorf("failed to get bookings: %w", err) } return bookings, total, nil } // generateDailyBreakdown generates daily financial breakdown for the given date range func (s *adminService) generateDailyBreakdown(startDate, endDate time.Time) ([]DailyFinancialSummary, error) { var breakdown []DailyFinancialSummary // Iterate through each day in the range for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) { dayStart := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) dayEnd := dayStart.AddDate(0, 0, 1) stats, err := s.bookingRepo.GetFinancialStats(dayStart, dayEnd) if err != nil { return nil, fmt.Errorf("failed to get daily stats for %v: %w", d, err) } breakdown = append(breakdown, DailyFinancialSummary{ Date: dayStart, Revenue: stats.TotalRevenue, Bookings: stats.TotalBookings, }) } return breakdown, nil } // generateMonthlyBreakdown generates monthly financial breakdown for the given date range func (s *adminService) generateMonthlyBreakdown(startDate, endDate time.Time) ([]MonthlyFinancialSummary, error) { var breakdown []MonthlyFinancialSummary // Start from the beginning of the start month current := time.Date(startDate.Year(), startDate.Month(), 1, 0, 0, 0, 0, startDate.Location()) endMonth := time.Date(endDate.Year(), endDate.Month(), 1, 0, 0, 0, 0, endDate.Location()) for !current.After(endMonth) { monthStart := current monthEnd := monthStart.AddDate(0, 1, 0) // Adjust for the actual date range if monthStart.Before(startDate) { monthStart = startDate } if monthEnd.After(endDate) { monthEnd = endDate } stats, err := s.bookingRepo.GetFinancialStats(monthStart, monthEnd) if err != nil { return nil, fmt.Errorf("failed to get monthly stats for %v: %w", current, err) } breakdown = append(breakdown, MonthlyFinancialSummary{ Month: current.Format("2006-01"), Revenue: stats.TotalRevenue, Bookings: stats.TotalBookings, }) current = current.AddDate(0, 1, 0) } return breakdown, nil }