package services

import (
	"errors"
	"time"

	"github.com/rs/zerolog/log"
	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/apperrors"
	"github.com/treytartt/honeydue-api/internal/config"
	"github.com/treytartt/honeydue-api/internal/dto/requests"
	"github.com/treytartt/honeydue-api/internal/dto/responses"
	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/repositories"
	"github.com/treytartt/honeydue-api/internal/task/predicates"
)

// Common errors (deprecated - kept for reference, now using apperrors package)
// Most errors have been migrated to apperrors, but some are still used by other handlers
// TODO: Migrate handlers to use apperrors instead of these constants
var (
	ErrResidenceNotFound      = errors.New("residence not found")
	ErrResidenceAccessDenied  = errors.New("you do not have access to this residence")
	ErrNotResidenceOwner      = errors.New("only the residence owner can perform this action")
	ErrCannotRemoveOwner      = errors.New("cannot remove the owner from the residence")
	ErrUserAlreadyMember      = errors.New("user is already a member of this residence")
	ErrShareCodeInvalid       = errors.New("invalid or expired share code")
	ErrShareCodeExpired       = errors.New("share code has expired")
	ErrPropertiesLimitReached = errors.New("you have reached the maximum number of properties for your subscription tier")
)

// ResidenceService handles residence business logic
type ResidenceService struct {
	residenceRepo       *repositories.ResidenceRepository
	userRepo            *repositories.UserRepository
	taskRepo            *repositories.TaskRepository
	subscriptionService *SubscriptionService
	config              *config.Config
}

// NewResidenceService creates a new residence service
func NewResidenceService(residenceRepo *repositories.ResidenceRepository, userRepo *repositories.UserRepository, cfg *config.Config) *ResidenceService {
	return &ResidenceService{
		residenceRepo: residenceRepo,
		userRepo:      userRepo,
		config:        cfg,
	}
}

// SetTaskRepository sets the task repository (used for task statistics)
func (s *ResidenceService) SetTaskRepository(taskRepo *repositories.TaskRepository) {
	s.taskRepo = taskRepo
}

// SetSubscriptionService sets the subscription service (used for tier limit enforcement)
func (s *ResidenceService) SetSubscriptionService(subService *SubscriptionService) {
	s.subscriptionService = subService
}

// GetResidence gets a residence by ID with access check.
// The `now` parameter is used for timezone-aware completion summary aggregation.
func (s *ResidenceService) GetResidence(residenceID, userID uint, now time.Time) (*responses.ResidenceResponse, error) {
	// Check access
	hasAccess, err := s.residenceRepo.HasAccess(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !hasAccess {
		return nil, apperrors.Forbidden("error.residence_access_denied")
	}

	residence, err := s.residenceRepo.FindByID(residenceID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.residence_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	resp := responses.NewResidenceResponse(residence)

	// Attach completion summary (honeycomb grid data)
	if s.taskRepo != nil {
		summary, err := s.taskRepo.GetCompletionSummary(residenceID, now, 10)
		if err != nil {
			log.Warn().Err(err).Uint("residence_id", residenceID).Msg("Failed to fetch completion summary")
		} else {
			resp.CompletionSummary = summary
		}
	}

	return &resp, nil
}

// ListResidences lists all residences accessible to a user
func (s *ResidenceService) ListResidences(userID uint) ([]responses.ResidenceResponse, error) {
	residences, err := s.residenceRepo.FindByUser(userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return responses.NewResidenceListResponse(residences), nil
}

// GetMyResidences returns residences with additional details (tasks, completions, etc.)
// This is the "my-residences" endpoint that returns richer data.
// The `now` parameter should be the start of day in the user's timezone for accurate overdue detection.
//
// NOTE: Summary statistics (TotalTasks, TotalOverdue, etc.) are calculated client-side
// from kanban data for performance. Only per-residence OverdueCount is returned from the server.
func (s *ResidenceService) GetMyResidences(userID uint, now time.Time) (*responses.MyResidencesResponse, error) {
	residences, err := s.residenceRepo.FindByUser(userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	residenceResponses := responses.NewResidenceListResponse(residences)

	// Get per-residence overdue counts for residence card badges
	if s.taskRepo != nil && len(residences) > 0 {
		residenceIDs := make([]uint, len(residences))
		for i, r := range residences {
			residenceIDs[i] = r.ID
		}

		overdueCounts, err := s.taskRepo.GetOverdueCountByResidence(residenceIDs, now)
		if err == nil && overdueCounts != nil {
			for i := range residenceResponses {
				if count, ok := overdueCounts[residenceResponses[i].ID]; ok {
					residenceResponses[i].OverdueCount = count
				}
			}
		}

		// P-01: Batch fetch completion summaries in 2 queries total instead of 2*N
		summaries, err := s.taskRepo.GetBatchCompletionSummaries(residenceIDs, now, 10)
		if err != nil {
			log.Warn().Err(err).Msg("Failed to fetch batch completion summaries")
		} else {
			for i := range residenceResponses {
				if summary, ok := summaries[residenceResponses[i].ID]; ok {
					residenceResponses[i].CompletionSummary = summary
				}
			}
		}
	}

	return &responses.MyResidencesResponse{
		Residences: residenceResponses,
	}, nil
}

// GetSummary returns just the task summary statistics for a user's residences.
// This is a lightweight endpoint for refreshing summary counts without full residence data.
//
// DEPRECATED: Summary statistics are now calculated client-side from kanban data.
// This endpoint only returns TotalResidences; other fields will be zero.
// Clients should use calculateSummaryFromKanban() instead.
func (s *ResidenceService) GetSummary(userID uint, now time.Time) (*responses.TotalSummary, error) {
	// Get residence IDs (lightweight - no preloads)
	residenceIDs, err := s.residenceRepo.FindResidenceIDsByUser(userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Summary statistics are calculated client-side from kanban data.
	// We only return TotalResidences here.
	return &responses.TotalSummary{
		TotalResidences: len(residenceIDs),
	}, nil
}

// getSummaryForUser returns an empty summary placeholder.
// DEPRECATED: Summary calculation has been removed from CRUD responses for performance.
// Clients should calculate summary from kanban data instead (which already includes all tasks).
// The summary field is kept in responses for backward compatibility but will always be empty.
// For actual summary data, use GetSummary() directly or rely on my-residences/kanban endpoints.
func (s *ResidenceService) getSummaryForUser(_ uint) responses.TotalSummary {
	// Return empty summary - clients should calculate from kanban data
	return responses.TotalSummary{}
}

// CreateResidence creates a new residence and returns it with updated summary
func (s *ResidenceService) CreateResidence(req *requests.CreateResidenceRequest, ownerID uint) (*responses.ResidenceWithSummaryResponse, error) {
	// Check subscription tier limits (if subscription service is wired up)
	if s.subscriptionService != nil {
		if err := s.subscriptionService.CheckLimit(ownerID, "properties"); err != nil {
			return nil, err
		}
	}

	isPrimary := true
	if req.IsPrimary != nil {
		isPrimary = *req.IsPrimary
	}

	// Set default country if not provided
	country := req.Country
	if country == "" {
		country = "USA"
	}

	residence := &models.Residence{
		OwnerID:         ownerID,
		Name:            req.Name,
		PropertyTypeID:  req.PropertyTypeID,
		StreetAddress:   req.StreetAddress,
		ApartmentUnit:   req.ApartmentUnit,
		City:            req.City,
		StateProvince:   req.StateProvince,
		PostalCode:      req.PostalCode,
		Country:         country,
		Bedrooms:        req.Bedrooms,
		Bathrooms:       req.Bathrooms,
		SquareFootage:   req.SquareFootage,
		LotSize:         req.LotSize,
		YearBuilt:       req.YearBuilt,
		Description:     req.Description,
		PurchaseDate:    req.PurchaseDate,
		PurchasePrice:   req.PurchasePrice,
		HeatingType:     req.HeatingType,
		CoolingType:     req.CoolingType,
		WaterHeaterType: req.WaterHeaterType,
		RoofType:        req.RoofType,
		ExteriorType:    req.ExteriorType,
		FlooringPrimary: req.FlooringPrimary,
		LandscapingType: req.LandscapingType,
		IsPrimary:       isPrimary,
		IsActive:        true,
	}

	// Apply boolean home profile fields
	if req.HasPool != nil {
		residence.HasPool = *req.HasPool
	}
	if req.HasSprinklerSystem != nil {
		residence.HasSprinklerSystem = *req.HasSprinklerSystem
	}
	if req.HasSeptic != nil {
		residence.HasSeptic = *req.HasSeptic
	}
	if req.HasFireplace != nil {
		residence.HasFireplace = *req.HasFireplace
	}
	if req.HasGarage != nil {
		residence.HasGarage = *req.HasGarage
	}
	if req.HasBasement != nil {
		residence.HasBasement = *req.HasBasement
	}
	if req.HasAttic != nil {
		residence.HasAttic = *req.HasAttic
	}

	if err := s.residenceRepo.Create(residence); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Reload with relations
	residence, err := s.residenceRepo.FindByID(residence.ID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Get updated summary
	summary := s.getSummaryForUser(ownerID)

	return &responses.ResidenceWithSummaryResponse{
		Data:    responses.NewResidenceResponse(residence),
		Summary: summary,
	}, nil
}

// UpdateResidence updates a residence and returns it with updated summary
func (s *ResidenceService) UpdateResidence(residenceID, userID uint, req *requests.UpdateResidenceRequest) (*responses.ResidenceWithSummaryResponse, error) {
	// Check ownership
	isOwner, err := s.residenceRepo.IsOwner(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !isOwner {
		return nil, apperrors.Forbidden("error.not_residence_owner")
	}

	residence, err := s.residenceRepo.FindByID(residenceID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.residence_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	// Apply updates (only non-nil fields)
	if req.Name != nil {
		residence.Name = *req.Name
	}
	if req.PropertyTypeID != nil {
		residence.PropertyTypeID = req.PropertyTypeID
	}
	if req.StreetAddress != nil {
		residence.StreetAddress = *req.StreetAddress
	}
	if req.ApartmentUnit != nil {
		residence.ApartmentUnit = *req.ApartmentUnit
	}
	if req.City != nil {
		residence.City = *req.City
	}
	if req.StateProvince != nil {
		residence.StateProvince = *req.StateProvince
	}
	if req.PostalCode != nil {
		residence.PostalCode = *req.PostalCode
	}
	if req.Country != nil {
		residence.Country = *req.Country
	}
	if req.Bedrooms != nil {
		residence.Bedrooms = req.Bedrooms
	}
	if req.Bathrooms != nil {
		residence.Bathrooms = req.Bathrooms
	}
	if req.SquareFootage != nil {
		residence.SquareFootage = req.SquareFootage
	}
	if req.LotSize != nil {
		residence.LotSize = req.LotSize
	}
	if req.YearBuilt != nil {
		residence.YearBuilt = req.YearBuilt
	}
	if req.Description != nil {
		residence.Description = *req.Description
	}
	if req.PurchaseDate != nil {
		residence.PurchaseDate = req.PurchaseDate
	}
	if req.PurchasePrice != nil {
		residence.PurchasePrice = req.PurchasePrice
	}
	if req.IsPrimary != nil {
		residence.IsPrimary = *req.IsPrimary
	}

	// Home Profile fields
	if req.HeatingType != nil {
		residence.HeatingType = req.HeatingType
	}
	if req.CoolingType != nil {
		residence.CoolingType = req.CoolingType
	}
	if req.WaterHeaterType != nil {
		residence.WaterHeaterType = req.WaterHeaterType
	}
	if req.RoofType != nil {
		residence.RoofType = req.RoofType
	}
	if req.HasPool != nil {
		residence.HasPool = *req.HasPool
	}
	if req.HasSprinklerSystem != nil {
		residence.HasSprinklerSystem = *req.HasSprinklerSystem
	}
	if req.HasSeptic != nil {
		residence.HasSeptic = *req.HasSeptic
	}
	if req.HasFireplace != nil {
		residence.HasFireplace = *req.HasFireplace
	}
	if req.HasGarage != nil {
		residence.HasGarage = *req.HasGarage
	}
	if req.HasBasement != nil {
		residence.HasBasement = *req.HasBasement
	}
	if req.HasAttic != nil {
		residence.HasAttic = *req.HasAttic
	}
	if req.ExteriorType != nil {
		residence.ExteriorType = req.ExteriorType
	}
	if req.FlooringPrimary != nil {
		residence.FlooringPrimary = req.FlooringPrimary
	}
	if req.LandscapingType != nil {
		residence.LandscapingType = req.LandscapingType
	}

	if err := s.residenceRepo.Update(residence); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Reload with relations
	residence, err = s.residenceRepo.FindByID(residence.ID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Get updated summary
	summary := s.getSummaryForUser(userID)

	return &responses.ResidenceWithSummaryResponse{
		Data:    responses.NewResidenceResponse(residence),
		Summary: summary,
	}, nil
}

// DeleteResidence soft-deletes a residence and returns updated summary
func (s *ResidenceService) DeleteResidence(residenceID, userID uint) (*responses.ResidenceDeleteWithSummaryResponse, error) {
	// Check ownership
	isOwner, err := s.residenceRepo.IsOwner(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !isOwner {
		return nil, apperrors.Forbidden("error.not_residence_owner")
	}

	if err := s.residenceRepo.Delete(residenceID); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Get updated summary
	summary := s.getSummaryForUser(userID)

	return &responses.ResidenceDeleteWithSummaryResponse{
		Data:    "residence deleted",
		Summary: summary,
	}, nil
}

// GenerateShareCode generates a new share code for a residence
func (s *ResidenceService) GenerateShareCode(residenceID, userID uint, expiresInHours int) (*responses.GenerateShareCodeResponse, error) {
	// Check ownership
	isOwner, err := s.residenceRepo.IsOwner(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !isOwner {
		return nil, apperrors.Forbidden("error.not_residence_owner")
	}

	// Default to 24 hours if not specified
	if expiresInHours <= 0 {
		expiresInHours = 24
	}

	shareCode, err := s.residenceRepo.CreateShareCode(residenceID, userID, time.Duration(expiresInHours)*time.Hour)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return &responses.GenerateShareCodeResponse{
		Message:   "Share code generated successfully",
		ShareCode: responses.NewShareCodeResponse(shareCode),
	}, nil
}

// GetShareCode retrieves the active share code for a residence (if any)
func (s *ResidenceService) GetShareCode(residenceID, userID uint) (*responses.ShareCodeResponse, error) {
	// Check access
	hasAccess, err := s.residenceRepo.HasAccess(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !hasAccess {
		return nil, apperrors.Forbidden("error.residence_access_denied")
	}

	shareCode, err := s.residenceRepo.GetActiveShareCode(residenceID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if shareCode == nil {
		return nil, nil
	}

	resp := responses.NewShareCodeResponse(shareCode)
	return &resp, nil
}

// GenerateSharePackage generates a share code and returns package metadata for .honeydue file
func (s *ResidenceService) GenerateSharePackage(residenceID, userID uint, expiresInHours int) (*responses.SharePackageResponse, error) {
	// Check ownership (only owners can share residences)
	isOwner, err := s.residenceRepo.IsOwner(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !isOwner {
		return nil, apperrors.Forbidden("error.not_residence_owner")
	}

	// Get residence details for the package
	residence, err := s.residenceRepo.FindByID(residenceID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Get the user who's sharing
	user, err := s.userRepo.FindByID(userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Default to 24 hours if not specified
	if expiresInHours <= 0 {
		expiresInHours = 24
	}

	// Generate the share code
	shareCode, err := s.residenceRepo.CreateShareCode(residenceID, userID, time.Duration(expiresInHours)*time.Hour)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return &responses.SharePackageResponse{
		ShareCode:     shareCode.Code,
		ResidenceName: residence.Name,
		SharedBy:      user.Email,
		ExpiresAt:     shareCode.ExpiresAt,
	}, nil
}

// JoinWithCode allows a user to join a residence using a share code
func (s *ResidenceService) JoinWithCode(code string, userID uint) (*responses.JoinResidenceResponse, error) {
	// Find the share code
	shareCode, err := s.residenceRepo.FindShareCodeByCode(code)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.share_code_invalid")
		}
		return nil, apperrors.Internal(err)
	}

	// Check if already a member
	hasAccess, err := s.residenceRepo.HasAccess(shareCode.ResidenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if hasAccess {
		return nil, apperrors.Conflict("error.user_already_member")
	}

	// Add user to residence
	if err := s.residenceRepo.AddUser(shareCode.ResidenceID, userID); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Mark share code as used (one-time use)
	if err := s.residenceRepo.DeactivateShareCode(shareCode.ID); err != nil {
		// Log the error but don't fail the join - the user has already been added
		// The code will just be usable by others until it expires
		log.Error().Err(err).Uint("code_id", shareCode.ID).Msg("Failed to deactivate share code after join")
	}

	// Get the residence with full details
	residence, err := s.residenceRepo.FindByID(shareCode.ResidenceID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Get updated summary for the user
	summary := s.getSummaryForUser(userID)

	return &responses.JoinResidenceResponse{
		Message:   "Successfully joined residence",
		Residence: responses.NewResidenceResponse(residence),
		Summary:   summary,
	}, nil
}

// GetResidenceUsers returns all users with access to a residence
func (s *ResidenceService) GetResidenceUsers(residenceID, userID uint) ([]responses.ResidenceUserResponse, error) {
	// Check access
	hasAccess, err := s.residenceRepo.HasAccess(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !hasAccess {
		return nil, apperrors.Forbidden("error.residence_access_denied")
	}

	users, err := s.residenceRepo.GetResidenceUsers(residenceID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	result := make([]responses.ResidenceUserResponse, len(users))
	for i, user := range users {
		result[i] = *responses.NewResidenceUserResponse(&user)
	}

	return result, nil
}

// RemoveUser removes a user from a residence (owner only)
func (s *ResidenceService) RemoveUser(residenceID, userIDToRemove, requestingUserID uint) error {
	// Check ownership
	isOwner, err := s.residenceRepo.IsOwner(residenceID, requestingUserID)
	if err != nil {
		return apperrors.Internal(err)
	}
	if !isOwner {
		return apperrors.Forbidden("error.not_residence_owner")
	}

	// Cannot remove the owner
	if userIDToRemove == requestingUserID {
		return apperrors.BadRequest("error.cannot_remove_owner")
	}

	// Check if the residence exists
	residence, err := s.residenceRepo.FindByIDSimple(residenceID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return apperrors.NotFound("error.residence_not_found")
		}
		return apperrors.Internal(err)
	}

	// Cannot remove the owner
	if userIDToRemove == residence.OwnerID {
		return apperrors.BadRequest("error.cannot_remove_owner")
	}

	if err := s.residenceRepo.RemoveUser(residenceID, userIDToRemove); err != nil {
		return apperrors.Internal(err)
	}

	return nil
}

// GetResidenceTypes returns all residence types
func (s *ResidenceService) GetResidenceTypes() ([]responses.ResidenceTypeResponse, error) {
	types, err := s.residenceRepo.GetAllResidenceTypes()
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	result := make([]responses.ResidenceTypeResponse, len(types))
	for i, t := range types {
		result[i] = *responses.NewResidenceTypeResponse(&t)
	}

	return result, nil
}

// TaskReportData represents task data for a report
type TaskReportData struct {
	ID           uint       `json:"id"`
	Title        string     `json:"title"`
	Description  string     `json:"description,omitempty"`
	Category     string     `json:"category"`
	Priority     string     `json:"priority"`
	Status       string     `json:"status"`
	DueDate      *time.Time `json:"due_date,omitempty"`
	IsCompleted  bool       `json:"is_completed"`
	IsCancelled  bool       `json:"is_cancelled"`
	IsArchived   bool       `json:"is_archived"`
}

// TasksReportResponse represents the generated tasks report
type TasksReportResponse struct {
	ResidenceID   uint             `json:"residence_id"`
	ResidenceName string           `json:"residence_name"`
	GeneratedAt   time.Time        `json:"generated_at"`
	TotalTasks    int              `json:"total_tasks"`
	Completed     int              `json:"completed"`
	Pending       int              `json:"pending"`
	Overdue       int              `json:"overdue"`
	Tasks         []TaskReportData `json:"tasks"`
}

// GenerateTasksReport generates a report of all tasks for a residence
func (s *ResidenceService) GenerateTasksReport(residenceID, userID uint) (*TasksReportResponse, error) {
	// Check access
	hasAccess, err := s.residenceRepo.HasAccess(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !hasAccess {
		return nil, apperrors.Forbidden("error.residence_access_denied")
	}

	// Get residence details
	residence, err := s.residenceRepo.FindByIDSimple(residenceID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.residence_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	// Get all tasks for the residence
	tasks, err := s.residenceRepo.GetTasksForReport(residenceID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	now := time.Now().UTC()
	report := &TasksReportResponse{
		ResidenceID:   residence.ID,
		ResidenceName: residence.Name,
		GeneratedAt:   now,
		TotalTasks:    len(tasks),
		Tasks:         make([]TaskReportData, len(tasks)),
	}

	for i, task := range tasks {
		// Use predicates from internal/task/predicates as single source of truth
		isCompleted := predicates.IsCompleted(&task)
		isOverdue := predicates.IsOverdue(&task, now)

		taskData := TaskReportData{
			ID:          task.ID,
			Title:       task.Title,
			Description: task.Description,
			IsCompleted: isCompleted,
			IsCancelled: task.IsCancelled,
			IsArchived:  task.IsArchived,
		}

		if task.Category != nil {
			taskData.Category = task.Category.Name
		}
		if task.Priority != nil {
			taskData.Priority = task.Priority.Name
		}
		if task.InProgress {
			taskData.Status = "In Progress"
		}
		// Use effective date for report (NextDueDate ?? DueDate)
		effectiveDate := predicates.EffectiveDate(&task)
		if effectiveDate != nil {
			taskData.DueDate = effectiveDate
		}

		report.Tasks[i] = taskData

		if isCompleted {
			report.Completed++
		} else if predicates.IsActive(&task) {
			report.Pending++
			if isOverdue {
				report.Overdue++
			}
		}
	}

	return report, nil
}
