package service

import (
	"errors"
	"math"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
	repository "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/repositories"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/validations"
	phaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/repositories"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"github.com/sirupsen/logrus"
	commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type DailyChecklistService interface {
	GetAll(ctx *fiber.Ctx, params *validation.Query) ([]DailyChecklistListItem, int64, error)
	GetOne(ctx *fiber.Ctx, id uint) (*entity.DailyChecklist, error)
	CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.DailyChecklist, error)
	UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.DailyChecklist, error)
	BulkUpdate(ctx *fiber.Ctx, req *validation.BulkStatusUpdate) ([]entity.DailyChecklist, error)
	DeleteOne(ctx *fiber.Ctx, id uint) error
	AssignPhases(ctx *fiber.Ctx, id uint, req *validation.AssignPhases) error
	AssignTasks(ctx *fiber.Ctx, id uint, req *validation.AssignTask) error
	RemoveAssignment(ctx *fiber.Ctx, id uint, employeeID uint) error
	GetTasks(ctx *fiber.Ctx, checklistID uint) ([]entity.DailyChecklistActivityTask, error)
	UpdateAssignment(ctx *fiber.Ctx, req *validation.UpdateAssignment) error
	GetChecklistPhaseIDs(ctx *fiber.Ctx, checklistID uint) ([]uint, error)
	GetDetail(ctx *fiber.Ctx, id uint) (*DailyChecklistDetail, error)
	GetSummary(ctx *fiber.Ctx, params *validation.SummaryQuery) ([]DailyChecklistSummary, error)
	GetReport(ctx *fiber.Ctx, params *validation.ReportQuery) ([]DailyChecklistReportItem, int64, error)
}

type dailyChecklistService struct {
	Log         *logrus.Logger
	Validate    *validator.Validate
	Repository  repository.DailyChecklistRepository
	PhaseRepo   phaseRepo.PhasesRepository
	DocumentSvc commonSvc.DocumentService
}

type DailyChecklistDocument struct {
	ID   uint
	Name string
	Size float64
	URL  string
}

type DailyChecklistDetail struct {
	Checklist         entity.DailyChecklist
	Phases            []entity.DailyChecklistPhase
	Tasks             []entity.DailyChecklistActivityTask
	AssignedEmployees []entity.Employee
	TotalActivities   int
	Progress          float64
	DocumentURLs      []DailyChecklistDocument
}

type DailyChecklistListItem struct {
	ID            uint
	Name          *string
	Date          time.Time
	Category      string
	Status        *string
	RejectReason  *string
	CreatedAt     time.Time
	UpdatedAt     time.Time
	Kandang       entity.KandangGroup
	TotalPhase    int
	TotalActivity int
	Progress      int
}

type DailyChecklistSummary struct {
	EmployeeID     uint
	EmployeeName   string
	KandangID      uint
	KandangName    string
	TotalActivity  int
	ActivityDone   int
	ActivityLeft   int
	CompletionRate int
	LastActivity   *time.Time
}

type DailyChecklistReportItem struct {
	AreaID          uint
	AreaName        string
	LocationID      uint
	LocationName    string
	KandangID       uint
	KandangName     string
	EmployeeID      uint
	EmployeeName    string
	PhaseName       string
	DailyActivities map[string]int
	Summary         DailyChecklistReportSummary
}

type DailyChecklistReportSummary struct {
	TotalChecklist    int
	JumlahHariEfektif int
	AbkPercentage     int
	KandangPercentage int
	Category          DailyChecklistReportCategory
}

type DailyChecklistReportCategory struct {
	Kurang int
	Cukup  int
	Baik   int
}

const (
	dailyChecklistDateLayout           = "2006-01-02"
	dailyChecklistCategoryEmptyKandang = "empty_kandang"
	dailyChecklistStatusRejected       = "REJECTED"
	dailyChecklistStatusDraft          = "DRAFT"
	dailyChecklistErrEmptyKandangExist = "DailyChecklist cannot be created because empty_kandang already exists for at least one date in range"
	dailyChecklistErrDateOverlapExist  = "DailyChecklist cannot be created because at least one date in range already has a checklist"
)

func NewDailyChecklistService(repo repository.DailyChecklistRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate, documentSvc commonSvc.DocumentService) DailyChecklistService {
	return &dailyChecklistService{
		Log:         utils.Log,
		Validate:    validate,
		Repository:  repo,
		PhaseRepo:   phaseRepo,
		DocumentSvc: documentSvc,
	}
}

func (s dailyChecklistService) withRelations(db *gorm.DB) *gorm.DB {
	return db.Preload("Kandang")
}

func (s dailyChecklistService) ensureChecklistAccess(c *fiber.Ctx, checklistID uint) error {
	if checklistID == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid checklist id")
	}

	db := s.Repository.DB().WithContext(c.Context()).
		Table("daily_checklists dc").
		Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
		Joins("JOIN locations loc ON loc.id = k.location_id").
		Joins("JOIN areas a ON a.id = loc.area_id").
		Where("dc.id = ?", checklistID).
		Where("dc.deleted_at IS NULL")

	scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
	if err != nil {
		return err
	}

	var count int64
	if err := scopedDB.Count(&count).Error; err != nil {
		return err
	}
	if count == 0 {
		return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
	}
	return nil
}

func (s dailyChecklistService) ensureKandangAccess(c *fiber.Ctx, kandangID uint) error {
	if kandangID == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang id")
	}

	db := s.Repository.DB().WithContext(c.Context()).
		Table("kandang_groups k").
		Joins("JOIN locations loc ON loc.id = k.location_id").
		Joins("JOIN areas a ON a.id = loc.area_id").
		Where("k.id = ?", kandangID)

	scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
	if err != nil {
		return err
	}

	var count int64
	if err := scopedDB.Count(&count).Error; err != nil {
		return err
	}
	if count == 0 {
		return fiber.NewError(fiber.StatusNotFound, "Kandang not found")
	}
	return nil
}

func (s dailyChecklistService) ensureTaskAccess(c *fiber.Ctx, taskID uint) error {
	if taskID == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid task id")
	}

	db := s.Repository.DB().WithContext(c.Context()).
		Table("daily_checklist_activity_tasks t").
		Joins("JOIN daily_checklists dc ON dc.id = t.checklist_id AND dc.deleted_at IS NULL").
		Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
		Joins("JOIN locations loc ON loc.id = k.location_id").
		Joins("JOIN areas a ON a.id = loc.area_id").
		Where("t.id = ?", taskID)

	scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
	if err != nil {
		return err
	}

	var count int64
	if err := scopedDB.Count(&count).Error; err != nil {
		return err
	}
	if count == 0 {
		return fiber.NewError(fiber.StatusNotFound, "Task not found")
	}
	return nil
}

func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([]DailyChecklistListItem, int64, error) {
	if err := s.Validate.Struct(params); err != nil {
		return nil, 0, err
	}

	offset := (params.Page - 1) * params.Limit

	db := s.Repository.DB().WithContext(c.Context()).
		Table("daily_checklists dc").
		Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
		Joins("JOIN locations loc ON loc.id = k.location_id").
		Joins("JOIN areas a ON a.id = loc.area_id").
		Where("dc.deleted_at IS NULL")

	var scopeErr error
	db, scopeErr = m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
	if scopeErr != nil {
		return nil, 0, scopeErr
	}

	if params.DateFrom != "" {
		dateFrom, err := time.Parse("2006-01-02", params.DateFrom)
		if err != nil {
			return nil, 0, fiber.NewError(fiber.StatusBadRequest, "invalid date_from format, use YYYY-MM-DD")
		}
		db = db.Where("dc.date >= ?", dateFrom)
	}

	if params.DateTo != "" {
		dateTo, err := time.Parse("2006-01-02", params.DateTo)
		if err != nil {
			return nil, 0, fiber.NewError(fiber.StatusBadRequest, "invalid date_to format, use YYYY-MM-DD")
		}
		db = db.Where("dc.date <= ?", dateTo)
	}

	if params.KandangID != nil {
		db = db.Where("dc.kandang_id = ?", *params.KandangID)
	}

	if params.Status != "" {
		db = db.Where("dc.status = ?", params.Status)
	}

	if params.Search != "" {
		re := regexp.MustCompile("[^a-zA-Z0-9]")
		normalizedSearch := re.ReplaceAllString(params.Search, "")
		if normalizedSearch != "" {
			like := "%" + normalizedSearch + "%"
			db = db.Where("(regexp_replace(k.name, '[^a-zA-Z0-9]', '', 'g') ILIKE ? OR regexp_replace(dc.category::text, '[^a-zA-Z0-9]', '', 'g') ILIKE ?)", like, like)
		}
	}

	countDB := db.Session(&gorm.Session{})
	var total int64
	if err := countDB.Count(&total).Error; err != nil {
		s.Log.Errorf("Failed to count dailyChecklists: %+v", err)
		return nil, 0, err
	}

	type dailyChecklistListRow struct {
		ID                   uint
		Name                 *string
		Date                 time.Time
		Category             string
		Status               *string
		RejectReason         *string
		CreatedAt            time.Time
		UpdatedAt            time.Time
		KandangID            uint
		TotalPhase           int64
		TotalActivity        int64
		TotalAssignments     int64
		CompletedAssignments int64
	}

	rows := make([]dailyChecklistListRow, 0)
	selectDB := db.Session(&gorm.Session{})
	if err := selectDB.
		Select(`
			dc.id,
			dc.name,
			dc.date,
			dc.category,
			dc.status,
			dc.reject_reason,
			dc.created_at,
			dc.updated_at,
			dc.kandang_id,
			COALESCE((
				SELECT COUNT(*)
				FROM daily_checklist_phases dcp
				WHERE dcp.checklist_id = dc.id
			), 0) AS total_phase,
			COALESCE((
				SELECT COUNT(pa.id)
				FROM daily_checklist_phases dcp
				JOIN phase_activities pa ON pa.phase_id = dcp.phase_id
				WHERE dcp.checklist_id = dc.id AND pa.deleted_at IS NULL
			), 0) AS total_activity,
			COALESCE((
				SELECT COUNT(*)
				FROM daily_checklist_activity_task_assignments dca
				JOIN daily_checklist_activity_tasks dcat ON dcat.id = dca.task_id
				WHERE dcat.checklist_id = dc.id
			), 0) AS total_assignments,
			COALESCE((
				SELECT COUNT(*)
				FROM daily_checklist_activity_task_assignments dca
				JOIN daily_checklist_activity_tasks dcat ON dcat.id = dca.task_id
				WHERE dcat.checklist_id = dc.id AND dca.checked
			), 0) AS completed_assignments`).
		Order("dc.date DESC, dc.created_at DESC").
		Offset(offset).
		Limit(params.Limit).
		Scan(&rows).Error; err != nil {
		s.Log.Errorf("Failed to get dailyChecklists: %+v", err)
		return nil, 0, err
	}

	kandangIDs := make([]uint, 0, len(rows))
	seen := make(map[uint]struct{})
	for _, row := range rows {
		if _, ok := seen[row.KandangID]; !ok {
			seen[row.KandangID] = struct{}{}
			kandangIDs = append(kandangIDs, row.KandangID)
		}
	}

	kandangMap := make(map[uint]entity.KandangGroup)
	if len(kandangIDs) > 0 {
		var kandangs []entity.KandangGroup
		if err := s.Repository.DB().WithContext(c.Context()).
			Where("id IN ?", kandangIDs).
			Preload("Location").
			Preload("Pic").
			Preload("CreatedUser").
			Find(&kandangs).Error; err != nil {
			s.Log.Errorf("Failed to get kandangs for daily checklist list: %+v", err)
			return nil, 0, err
		}
		for _, kandang := range kandangs {
			kandangMap[kandang.Id] = kandang
		}
	}

	items := make([]DailyChecklistListItem, len(rows))
	for i, row := range rows {
		progress := 0
		if row.TotalAssignments > 0 {
			progress = int(math.Round(float64(row.CompletedAssignments) / float64(row.TotalAssignments) * 100))
		}

		items[i] = DailyChecklistListItem{
			ID:            row.ID,
			Name:          row.Name,
			Date:          row.Date,
			Category:      row.Category,
			Status:        row.Status,
			RejectReason:  row.RejectReason,
			CreatedAt:     row.CreatedAt,
			UpdatedAt:     row.UpdatedAt,
			Kandang:       kandangMap[row.KandangID],
			TotalPhase:    int(row.TotalPhase),
			TotalActivity: int(row.TotalActivity),
			Progress:      progress,
		}
	}

	return items, total, nil
}

func (s dailyChecklistService) GetOne(c *fiber.Ctx, id uint) (*entity.DailyChecklist, error) {
	if err := s.ensureChecklistAccess(c, id); err != nil {
		return nil, err
	}
	dailyChecklist, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
	}
	if err != nil {
		s.Log.Errorf("Failed get dailyChecklist by id: %+v", err)
		return nil, err
	}
	return dailyChecklist, nil
}

func (s dailyChecklistService) GetDetail(c *fiber.Ctx, id uint) (*DailyChecklistDetail, error) {
	checklist, err := s.GetOne(c, id)
	if err != nil {
		return nil, err
	}

	db := s.Repository.DB().WithContext(c.Context())

	var phases []entity.DailyChecklistPhase
	if err := db.
		Where("checklist_id = ?", id).
		Preload("Phase", func(tx *gorm.DB) *gorm.DB {
			return tx.Preload("Activities")
		}).
		Order("created_at ASC").
		Find(&phases).Error; err != nil {
		s.Log.Errorf("Failed to get phases for daily checklist %d: %+v", id, err)
		return nil, err
	}

	var tasks []entity.DailyChecklistActivityTask
	if err := db.
		Where("checklist_id = ?", id).
		Preload("Phase").
		Preload("PhaseActivity").
		Preload("Assignments", func(tx *gorm.DB) *gorm.DB {
			return tx.Preload("Employee")
		}).
		Order("created_at ASC").
		Find(&tasks).Error; err != nil {
		s.Log.Errorf("Failed to get tasks for daily checklist %d: %+v", id, err)
		return nil, err
	}

	assignedEmployees := collectAssignedEmployees(tasks)

	totalActivities := 0
	for _, phase := range phases {
		totalActivities += len(phase.Phase.Activities)
	}

	var totalAssignments, completedAssignments int
	for _, task := range tasks {
		for _, assignment := range task.Assignments {
			totalAssignments++
			if assignment.Checked {
				completedAssignments++
			}
		}
	}

	var progress float64
	if totalAssignments > 0 {
		progress = math.Round((float64(completedAssignments) / float64(totalAssignments)) * 100)
	}

	documentURLs := make([]DailyChecklistDocument, 0)
	if s.DocumentSvc != nil {
		documents, err := s.DocumentSvc.ListByTarget(c.Context(), string(utils.DocumentTypeDailyChecklist), uint64(id))
		if err != nil {
			s.Log.Errorf("Failed to list documents for daily checklist %d: %+v", id, err)
			return nil, err
		}

		for _, doc := range documents {
			url, err := s.DocumentSvc.PresignURL(c.Context(), doc, 0)
			if err != nil {
				s.Log.Errorf("Failed to presign document %d for daily checklist %d: %+v", doc.Id, id, err)
				continue
			}
			documentURLs = append(documentURLs, DailyChecklistDocument{
				ID:   doc.Id,
				Name: doc.Name,
				Size: doc.Size,
				URL:  url,
			})
		}
	}

	return &DailyChecklistDetail{
		Checklist:         *checklist,
		Phases:            phases,
		Tasks:             tasks,
		AssignedEmployees: assignedEmployees,
		TotalActivities:   totalActivities,
		Progress:          progress,
		DocumentURLs:      documentURLs,
	}, nil
}

func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.DailyChecklist, error) {
	if err := s.Validate.Struct(req); err != nil {
		return nil, err
	}
	if err := s.ensureKandangAccess(c, req.KandangId); err != nil {
		return nil, err
	}

	date, err := time.Parse(dailyChecklistDateLayout, strings.TrimSpace(req.Date))
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "invalid date format, use YYYY-MM-DD")
	}

	status := req.Status
	category := req.Category
	endDate := date

	if req.EmptyKandang {
		if strings.TrimSpace(req.EmptyKandangEndDate) == "" {
			return nil, fiber.NewError(fiber.StatusBadRequest, "empty_kandang_end_date is required when empty_kandang is true")
		}

		endDate, err = time.Parse(dailyChecklistDateLayout, strings.TrimSpace(req.EmptyKandangEndDate))
		if err != nil {
			return nil, fiber.NewError(fiber.StatusBadRequest, "invalid empty_kandang_end_date format, use YYYY-MM-DD")
		}
		if endDate.Before(date) {
			return nil, fiber.NewError(fiber.StatusBadRequest, "empty_kandang_end_date must be greater than or equal to date")
		}

		category = dailyChecklistCategoryEmptyKandang
	}

	targetID := uint(0)

	err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
		if err := s.lockKandangForChecklistCreation(tx, req.KandangId); err != nil {
			return err
		}

		if req.EmptyKandang {
			if err := s.validateNoChecklistOverlapForEmptyKandang(tx, req.KandangId, date, endDate); err != nil {
				return err
			}
			return s.createBulkDailyChecklists(tx, req.KandangId, date, endDate, category, status, &targetID)
		}

		if err := s.validateNoEmptyKandangConflict(tx, req.KandangId, date, endDate); err != nil {
			return err
		}

		return s.createOrReuseSingleDailyChecklist(tx, req.KandangId, date, category, status, &targetID)
	})
	if err != nil {
		s.Log.Errorf("Failed to create/upsert dailyChecklist: %+v", err)
		return nil, err
	}

	return s.GetOne(c, targetID)
}

func (s *dailyChecklistService) lockKandangForChecklistCreation(tx *gorm.DB, kandangID uint) error {
	if kandangID == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang id")
	}

	var lockedKandangID uint
	query := tx.Table("kandang_groups").Select("id").Where("id = ?", kandangID)
	if tx.Dialector.Name() != "sqlite" {
		query = query.Clauses(clause.Locking{Strength: "UPDATE"})
	}

	if err := query.Take(&lockedKandangID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "Kandang not found")
		}
		return err
	}
	return nil
}

func (s *dailyChecklistService) validateNoChecklistOverlapForEmptyKandang(tx *gorm.DB, kandangID uint, startDate, endDate time.Time) error {
	var conflictCount int64
	if err := tx.Model(&entity.DailyChecklist{}).
		Where("kandang_id = ? AND date BETWEEN ? AND ? AND deleted_at IS NULL", kandangID, startDate, endDate).
		Count(&conflictCount).Error; err != nil {
		return err
	}

	if conflictCount > 0 {
		return fiber.NewError(fiber.StatusConflict, dailyChecklistErrDateOverlapExist)
	}

	return nil
}

func (s *dailyChecklistService) validateNoEmptyKandangConflict(tx *gorm.DB, kandangID uint, startDate, endDate time.Time) error {
	var conflictCount int64
	if err := tx.Model(&entity.DailyChecklist{}).
		Where("kandang_id = ? AND date BETWEEN ? AND ? AND category = ? AND deleted_at IS NULL", kandangID, startDate, endDate, dailyChecklistCategoryEmptyKandang).
		Count(&conflictCount).Error; err != nil {
		return err
	}

	if conflictCount > 0 {
		return fiber.NewError(fiber.StatusConflict, dailyChecklistErrEmptyKandangExist)
	}

	return nil
}

func (s *dailyChecklistService) createOrReuseSingleDailyChecklist(tx *gorm.DB, kandangID uint, date time.Time, category, status string, targetID *uint) error {
	existing := new(entity.DailyChecklist)
	err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", date, kandangID, category, dailyChecklistStatusRejected).
		Take(existing).Error
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}

	if err == nil {
		if err := tx.Model(&entity.DailyChecklist{}).
			Where("id = ?", existing.Id).
			Update("updated_at", time.Now()).Error; err != nil {
			return err
		}

		*targetID = existing.Id
		return nil
	}

	createStatus := status
	var rejectedCount int64
	if err := tx.Model(&entity.DailyChecklist{}).
		Where("date = ? AND kandang_id = ? AND category = ? AND status = ? AND deleted_at IS NULL", date, kandangID, category, dailyChecklistStatusRejected).
		Count(&rejectedCount).Error; err != nil {
		return err
	}
	if rejectedCount > 0 {
		createStatus = dailyChecklistStatusDraft
	}

	createBody := &entity.DailyChecklist{
		KandangId: kandangID,
		Date:      date,
		Category:  category,
		Status:    &createStatus,
	}

	if err := tx.Create(createBody).Error; err != nil {
		// Handle concurrent insert for active checklist with same key.
		if findErr := tx.
			Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", date, kandangID, category, dailyChecklistStatusRejected).
			Take(existing).Error; findErr == nil {
			*targetID = existing.Id
			return nil
		}
		return err
	}

	*targetID = createBody.Id
	return nil
}

func (s *dailyChecklistService) createBulkDailyChecklists(tx *gorm.DB, kandangID uint, startDate, endDate time.Time, category, status string, targetID *uint) error {
	var conflictCount int64
	if err := tx.Model(&entity.DailyChecklist{}).
		Where("kandang_id = ? AND category = ? AND date BETWEEN ? AND ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", kandangID, category, startDate, endDate, dailyChecklistStatusRejected).
		Count(&conflictCount).Error; err != nil {
		return err
	}
	if conflictCount > 0 {
		return fiber.NewError(fiber.StatusConflict, "DailyChecklist already exists for at least one date in range")
	}

	for currentDate := startDate; !currentDate.After(endDate); currentDate = currentDate.AddDate(0, 0, 1) {
		createStatus := status
		var rejectedCount int64
		if err := tx.Model(&entity.DailyChecklist{}).
			Where("date = ? AND kandang_id = ? AND category = ? AND status = ? AND deleted_at IS NULL", currentDate, kandangID, category, dailyChecklistStatusRejected).
			Count(&rejectedCount).Error; err != nil {
			return err
		}
		if rejectedCount > 0 {
			createStatus = dailyChecklistStatusDraft
		}

		createBody := &entity.DailyChecklist{
			KandangId: kandangID,
			Date:      currentDate,
			Category:  category,
			Status:    &createStatus,
		}

		if err := tx.Create(createBody).Error; err != nil {
			// Handle concurrent insert for active checklist in same date range.
			var existingActiveCount int64
			checkErr := tx.Model(&entity.DailyChecklist{}).
				Where("date = ? AND kandang_id = ? AND category = ? AND (status IS NULL OR status <> ?) AND deleted_at IS NULL", currentDate, kandangID, category, dailyChecklistStatusRejected).
				Count(&existingActiveCount).Error
			if checkErr == nil && existingActiveCount > 0 {
				return fiber.NewError(fiber.StatusConflict, "DailyChecklist already exists for at least one date in range")
			}
			return err
		}

		if currentDate.Equal(startDate) {
			*targetID = createBody.Id
		}
	}

	return nil
}

func (s dailyChecklistService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.DailyChecklist, error) {
	if err := s.Validate.Struct(req); err != nil {
		return nil, err
	}
	if err := s.ensureChecklistAccess(c, id); err != nil {
		return nil, err
	}

	deletedIDs := make([]uint, 0)
	if req.DeletedDocumentIDs != nil {
		parts := strings.Split(*req.DeletedDocumentIDs, ",")
		for _, part := range parts {
			part = strings.TrimSpace(part)
			if part == "" {
				continue
			}
			parsedID, err := strconv.ParseUint(part, 10, 64)
			if err != nil {
				return nil, fiber.NewError(fiber.StatusBadRequest, "invalid deleted_document_ids")
			}
			deletedIDs = append(deletedIDs, uint(parsedID))
		}
	}

	updateBody := map[string]any{
		"status": req.Status,
	}

	if req.RejectReason != nil {
		updateBody["reject_reason"] = *req.RejectReason
	}

	actorID, err := m.ActorIDFromContext(c)
	if err != nil {
		return &entity.DailyChecklist{}, fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
	}

	if len(deletedIDs) > 0 && s.DocumentSvc != nil {
		if err := s.DocumentSvc.DeleteDocuments(c.Context(), deletedIDs, true); err != nil {
			s.Log.Errorf("Failed to delete daily checklist documents: %+v", err)
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete daily checklist documents")
		}
	}

	if len(req.Documents) > 0 {
		documentFiles := make([]commonSvc.DocumentFile, 0, len(req.Documents))
		for idx, file := range req.Documents {
			documentFiles = append(documentFiles, commonSvc.DocumentFile{
				File:  file,
				Type:  string(utils.DocumentTypeDailyChecklist),
				Index: &idx,
			})
		}

		_, err := s.DocumentSvc.UploadDocuments(c.Context(), commonSvc.DocumentUploadRequest{
			DocumentableType: string(utils.DocumentTypeDailyChecklist),
			DocumentableID:   uint64(id),
			CreatedBy:        &actorID,
			Files:            documentFiles,
		})
		if err != nil {
			s.Log.Errorf("Failed to upload daily checklist documents: %+v", err)
			return &entity.DailyChecklist{}, fiber.NewError(fiber.StatusInternalServerError, "Failed to upload daily checklist documents")
		}
	}

	if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		s.Log.Errorf("Failed to update dailyChecklist: %+v", err)
		return nil, err
	}

	return s.GetOne(c, id)
}

func (s dailyChecklistService) BulkUpdate(c *fiber.Ctx, req *validation.BulkStatusUpdate) ([]entity.DailyChecklist, error) {
	if err := s.Validate.Struct(req); err != nil {
		return nil, err
	}

	status := strings.ToUpper(strings.TrimSpace(req.Status))
	if status != "APPROVED" && status != "REJECTED" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "status must be APPROVED or REJECTED")
	}

	ids, err := parseChecklistIDs(req.IDs)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
	}
	if len(ids) == 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "ids cannot be empty")
	}

	scopedIDs, err := s.Repository.ListScopedChecklistIDs(c, ids)
	if err != nil {
		s.Log.Errorf("Failed to validate daily checklist scope for bulk update: %+v", err)
		return nil, err
	}
	if len(scopedIDs) != len(ids) {
		return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
	}

	var rejectReason *string
	if status == "REJECTED" {
		rejectReason = req.RejectReason
	}

	if err := s.Repository.BulkUpdateStatus(c.Context(), ids, status, rejectReason); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		s.Log.Errorf("Failed to bulk update daily checklist status: %+v", err)
		return nil, err
	}

	updated, err := s.Repository.ListByIDsWithKandang(c.Context(), ids)
	if err != nil {
		s.Log.Errorf("Failed to fetch updated daily checklists: %+v", err)
		return nil, err
	}
	if len(updated) != len(ids) {
		return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
	}

	orderByID := make(map[uint]int, len(ids))
	for idx, id := range ids {
		orderByID[id] = idx
	}

	sort.Slice(updated, func(i, j int) bool {
		return orderByID[updated[i].Id] < orderByID[updated[j].Id]
	})

	return updated, nil
}

func (s dailyChecklistService) DeleteOne(c *fiber.Ctx, id uint) error {
	if err := s.ensureChecklistAccess(c, id); err != nil {
		return err
	}
	actorID, err := m.ActorIDFromContext(c)
	if err != nil {
		return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
	}

	if err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
		updateResult := tx.Model(&entity.DailyChecklist{}).
			Where("id = ?", id).
			Updates(map[string]any{
				"deleted_by": actorID,
				"updated_at": time.Now(),
			})
		if updateResult.Error != nil {
			return updateResult.Error
		}
		if updateResult.RowsAffected == 0 {
			return gorm.ErrRecordNotFound
		}

		deleteResult := tx.Delete(&entity.DailyChecklist{}, id)
		if deleteResult.Error != nil {
			return deleteResult.Error
		}
		if deleteResult.RowsAffected == 0 {
			return gorm.ErrRecordNotFound
		}

		return nil
	}); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		s.Log.Errorf("Failed to delete dailyChecklist: %+v", err)
		return err
	}
	return nil
}

func (s dailyChecklistService) AssignPhases(c *fiber.Ctx, id uint, req *validation.AssignPhases) error {
	if err := s.Validate.Struct(req); err != nil {
		return err
	}
	if err := s.ensureChecklistAccess(c, id); err != nil {
		return err
	}

	if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		return err
	}

	phaseIDs, err := parsePhaseIDs(req.PhaseIDs)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	if len(phaseIDs) > 0 {
		phases, err := s.PhaseRepo.GetByIDs(c.Context(), phaseIDs, nil)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return fiber.NewError(fiber.StatusBadRequest, "Phase not found")
			}
			return err
		}
		if len(phases) != len(phaseIDs) {
			return fiber.NewError(fiber.StatusBadRequest, "Phase not found")
		}
	}

	db := s.Repository.DB()
	if err := db.WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("checklist_id = ?", id).Delete(&entity.DailyChecklistPhase{}).Error; err != nil {
			return err
		}

		if len(phaseIDs) == 0 {
			return nil
		}

		records := make([]entity.DailyChecklistPhase, 0, len(phaseIDs))
		for _, pid := range phaseIDs {
			records = append(records, entity.DailyChecklistPhase{
				ChecklistId: id,
				PhaseId:     pid,
			})
		}

		if err := tx.Create(&records).Error; err != nil {
			return err
		}

		if err := tx.Where("checklist_id = ?", id).Delete(&entity.DailyChecklistActivityTask{}).Error; err != nil {
			return err
		}

		var activities []entity.PhaseActivity
		if err := tx.Where("phase_id IN ?", phaseIDs).Find(&activities).Error; err != nil {
			return err
		}

		activityRecords := make([]entity.DailyChecklistActivityTask, 0, len(activities))
		for _, activity := range activities {
			activityRecords = append(activityRecords, entity.DailyChecklistActivityTask{
				ChecklistId:     id,
				PhaseId:         activity.PhaseId,
				PhaseActivityId: activity.Id,
				TimeType:        activity.TimeType,
			})
		}

		if len(activityRecords) == 0 {
			return nil
		}

		return tx.Create(&activityRecords).Error
	}); err != nil {
		s.Log.Errorf("Failed to assign phases to daily checklist: %+v", err)
		return err
	}

	return nil
}

func (s dailyChecklistService) RemoveAssignment(c *fiber.Ctx, id uint, employeeID uint) error {
	if err := s.ensureChecklistAccess(c, id); err != nil {
		return err
	}
	if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		return err
	}

	if employeeID == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid employee id")
	}

	db := s.Repository.DB()
	if err := db.WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
		var tasks []entity.DailyChecklistActivityTask
		if err := tx.Where("checklist_id = ?", id).Find(&tasks).Error; err != nil {
			return err
		}

		if len(tasks) == 0 {
			return fiber.NewError(fiber.StatusBadRequest, "No activity tasks found for this checklist")
		}

		taskIDs := collectTaskIDs(tasks)
		return tx.Where("task_id IN ? AND employee_id = ?", taskIDs, employeeID).
			Delete(&entity.DailyChecklistActivityTaskAssignment{}).Error
	}); err != nil {
		s.Log.Errorf("Failed to remove assignment: %+v", err)
		return err
	}

	return nil
}

func (s dailyChecklistService) GetTasks(c *fiber.Ctx, checklistID uint) ([]entity.DailyChecklistActivityTask, error) {
	if checklistID == 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
	}
	if err := s.ensureChecklistAccess(c, checklistID); err != nil {
		return nil, err
	}

	if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		return nil, err
	}

	var tasks []entity.DailyChecklistActivityTask
	if err := s.Repository.DB().WithContext(c.Context()).
		Where("checklist_id = ?", checklistID).
		Order("created_at ASC").
		Find(&tasks).Error; err != nil {
		s.Log.Errorf("Failed to get daily checklist tasks: %+v", err)
		return nil, err
	}

	return tasks, nil
}

func (s dailyChecklistService) GetChecklistPhaseIDs(c *fiber.Ctx, checklistID uint) ([]uint, error) {
	if checklistID == 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
	}
	if err := s.ensureChecklistAccess(c, checklistID); err != nil {
		return nil, err
	}

	if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		return nil, err
	}

	var phases []entity.DailyChecklistPhase
	if err := s.Repository.DB().WithContext(c.Context()).
		Where("checklist_id = ?", checklistID).
		Order("created_at ASC").
		Find(&phases).Error; err != nil {
		s.Log.Errorf("Failed to get daily checklist phases: %+v", err)
		return nil, err
	}

	phaseIDs := make([]uint, len(phases))
	for i, p := range phases {
		phaseIDs[i] = p.PhaseId
	}

	return phaseIDs, nil
}

func (s dailyChecklistService) UpdateAssignment(c *fiber.Ctx, req *validation.UpdateAssignment) error {
	if err := s.Validate.Struct(req); err != nil {
		return err
	}
	if err := s.ensureTaskAccess(c, req.TaskID); err != nil {
		return err
	}

	task := new(entity.DailyChecklistActivityTask)
	if err := s.Repository.DB().WithContext(c.Context()).First(task, req.TaskID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "Task not found")
		}
		return err
	}

	if req.EmployeeID == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid employee id")
	}

	updates := map[string]any{"updated_at": time.Now()}
	if req.Checked != nil {
		updates["checked"] = *req.Checked
	}
	if req.Note != nil {
		updates["note"] = *req.Note
	}

	return s.Repository.DB().WithContext(c.Context()).Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "task_id"}, {Name: "employee_id"}},
		DoUpdates: clause.Assignments(updates),
	}).Create(&entity.DailyChecklistActivityTaskAssignment{
		TaskId:     req.TaskID,
		EmployeeId: req.EmployeeID,
		Checked:    req.Checked != nil && *req.Checked,
		Note:       req.Note,
	}).Error
}

func parsePhaseIDs(raw string) ([]uint, error) {
	parts := strings.Split(raw, ",")
	result := make([]uint, 0, len(parts))
	seen := make(map[uint]struct{})

	for _, part := range parts {
		value := strings.TrimSpace(part)
		if value == "" {
			continue
		}

		num, err := strconv.ParseUint(value, 10, 64)
		if err != nil {
			return nil, errors.New("invalid phase id: " + value)
		}
		u := uint(num)
		if _, ok := seen[u]; ok {
			continue
		}
		seen[u] = struct{}{}
		result = append(result, u)
	}

	return result, nil
}

func parseChecklistIDs(raw string) ([]uint, error) {
	parts := strings.Split(raw, ",")
	result := make([]uint, 0, len(parts))
	seen := make(map[uint]struct{})

	for _, part := range parts {
		value := strings.TrimSpace(part)
		if value == "" {
			continue
		}

		num, err := strconv.ParseUint(value, 10, 64)
		if err != nil || num == 0 {
			return nil, errors.New("invalid daily checklist id: " + value)
		}

		u := uint(num)
		if _, ok := seen[u]; ok {
			continue
		}
		seen[u] = struct{}{}
		result = append(result, u)
	}

	return result, nil
}

func parseIDs(raw string) ([]uint, error) {
	parts := strings.Split(raw, ",")
	result := make([]uint, 0, len(parts))
	seen := make(map[uint]struct{})

	for _, part := range parts {
		value := strings.TrimSpace(part)
		if value == "" {
			continue
		}

		num, err := strconv.ParseUint(value, 10, 64)
		if err != nil {
			return nil, errors.New("invalid employee id: " + value)
		}
		u := uint(num)
		if _, ok := seen[u]; ok {
			continue
		}
		seen[u] = struct{}{}
		result = append(result, u)
	}

	return result, nil
}

func collectTaskIDs(tasks []entity.DailyChecklistActivityTask) []uint {
	result := make([]uint, len(tasks))
	for i, task := range tasks {
		result[i] = task.Id
	}
	return result
}

func collectAssignedEmployees(tasks []entity.DailyChecklistActivityTask) []entity.Employee {
	employeeMap := make(map[uint]entity.Employee)
	for _, task := range tasks {
		for _, assignment := range task.Assignments {
			if assignment.Employee.Id == 0 {
				continue
			}
			if _, exists := employeeMap[assignment.Employee.Id]; exists {
				continue
			}
			employeeMap[assignment.Employee.Id] = assignment.Employee
		}
	}

	employees := make([]entity.Employee, 0, len(employeeMap))
	for _, emp := range employeeMap {
		employees = append(employees, emp)
	}

	sort.Slice(employees, func(i, j int) bool {
		return employees[i].Id < employees[j].Id
	})

	return employees
}
func (s dailyChecklistService) AssignTasks(c *fiber.Ctx, id uint, req *validation.AssignTask) error {
	if err := s.Validate.Struct(req); err != nil {
		return err
	}
	if err := s.ensureChecklistAccess(c, id); err != nil {
		return err
	}

	employeeIDs, err := parseIDs(req.EmployeeIDs)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	if len(employeeIDs) == 0 {
		return fiber.NewError(fiber.StatusBadRequest, "employee_ids cannot be empty")
	}

	if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
		}
		return err
	}

	db := s.Repository.DB()
	if err := db.WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
		var tasks []entity.DailyChecklistActivityTask
		if err := tx.Where("checklist_id = ?", id).Find(&tasks).Error; err != nil {
			return err
		}

		if len(tasks) == 0 {
			return fiber.NewError(fiber.StatusBadRequest, "No activity tasks found for this checklist")
		}

		assignments := make([]entity.DailyChecklistActivityTaskAssignment, 0, len(tasks)*len(employeeIDs))
		for _, task := range tasks {
			for _, empID := range employeeIDs {
				assignments = append(assignments, entity.DailyChecklistActivityTaskAssignment{
					TaskId:     task.Id,
					EmployeeId: empID,
				})
			}
		}

		return tx.Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "task_id"}, {Name: "employee_id"}},
			DoUpdates: clause.Assignments(map[string]any{"updated_at": time.Now()}),
		}).Create(&assignments).Error
	}); err != nil {
		s.Log.Errorf("Failed to assign tasks to daily checklist: %+v", err)
		return err
	}

	return nil
}

func (s dailyChecklistService) GetSummary(c *fiber.Ctx, params *validation.SummaryQuery) ([]DailyChecklistSummary, error) {
	if err := s.Validate.Struct(params); err != nil {
		return nil, err
	}

	dateFrom, err := time.Parse("2006-01-02", params.DateFrom)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "invalid date_from format, use YYYY-MM-DD")
	}

	dateTo, err := time.Parse("2006-01-02", params.DateTo)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "invalid date_to format, use YYYY-MM-DD")
	}

	type summaryRow struct {
		EmployeeID    uint
		EmployeeName  string
		KandangID     uint
		KandangName   string
		TotalActivity int64
		ActivityDone  int64
		ActivityLeft  int64
		LastActivity  *time.Time
	}

	rows := make([]summaryRow, 0)
	db := s.Repository.DB().WithContext(c.Context()).
		Table("daily_checklist_activity_task_assignments AS a").
		Select(`
			a.employee_id,
			e.name AS employee_name,
			d.kandang_id,
			k.name AS kandang_name,
			COUNT(*) AS total_activity,
			SUM(CASE WHEN a.checked THEN 1 ELSE 0 END) AS activity_done,
			SUM(CASE WHEN NOT a.checked THEN 1 ELSE 0 END) AS activity_left,
			MAX(a.updated_at) AS last_activity`).
		Joins("JOIN daily_checklist_activity_tasks t ON t.id = a.task_id").
		Joins("JOIN daily_checklists d ON d.id = t.checklist_id AND d.deleted_at IS NULL").
		Joins("JOIN kandang_groups k ON k.id = d.kandang_id").
		Joins("JOIN employees e ON e.id = a.employee_id").
		Joins("JOIN locations loc ON loc.id = k.location_id").
		Joins("JOIN areas ar ON ar.id = loc.area_id").
		Where("d.date BETWEEN ? AND ? AND d.status = ?", dateFrom, dateTo, "APPROVED")

	var scopeErr error
	db, scopeErr = m.ApplyLocationAreaScope(c, db, "loc.id", "ar.id")
	if scopeErr != nil {
		return nil, scopeErr
	}

	if params.Category != "" {
		db = db.Where("d.category = ?", params.Category)
	}

	if params.KandangID != nil {
		db = db.Where("d.kandang_id = ?", *params.KandangID)
	}

	if err := db.
		Group("a.employee_id, e.name, d.kandang_id, k.name").
		Order("e.name ASC").
		Find(&rows).Error; err != nil {
		s.Log.Errorf("Failed to get daily checklist summary: %+v", err)
		return nil, err
	}

	summaries := make([]DailyChecklistSummary, len(rows))
	for i, row := range rows {
		completionRate := 0
		if row.TotalActivity > 0 {
			completionRate = int(math.Round(float64(row.ActivityDone) / float64(row.TotalActivity) * 100))
		}

		summaries[i] = DailyChecklistSummary{
			EmployeeID:     row.EmployeeID,
			EmployeeName:   row.EmployeeName,
			KandangID:      row.KandangID,
			KandangName:    row.KandangName,
			TotalActivity:  int(row.TotalActivity),
			ActivityDone:   int(row.ActivityDone),
			ActivityLeft:   int(row.ActivityLeft),
			CompletionRate: completionRate,
			LastActivity:   row.LastActivity,
		}
	}

	return summaries, nil
}

func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.ReportQuery) ([]DailyChecklistReportItem, int64, error) {
	if err := s.Validate.Struct(params); err != nil {
		return nil, 0, err
	}

	locationScope, err := m.ResolveLocationScope(c, s.Repository.DB())
	if err != nil {
		return nil, 0, err
	}
	areaScope, err := m.ResolveAreaScope(c, s.Repository.DB())
	if err != nil {
		return nil, 0, err
	}

	offset := (params.Page - 1) * params.Limit

	buildBase := func() *gorm.DB {
		db := s.Repository.DB().WithContext(c.Context()).
			Table("daily_checklist_activity_task_assignments AS dca").
			Joins("JOIN daily_checklist_activity_tasks dcat ON dcat.id = dca.task_id").
			Joins("JOIN daily_checklists dc ON dc.id = dcat.checklist_id AND dc.deleted_at IS NULL").
			Joins("JOIN employees e ON e.id = dca.employee_id").
			Joins("JOIN kandang_groups k ON k.id = dc.kandang_id").
			Joins("JOIN locations loc ON loc.id = k.location_id").
			Joins("JOIN areas a ON a.id = loc.area_id").
			Joins("JOIN phases p ON p.id = dcat.phase_id").
			Where("EXTRACT(MONTH FROM dc.date) = ?", params.Month).
			Where("EXTRACT(YEAR FROM dc.date) = ?", params.Year).
			Where("dc.status = ?", "APPROVED")

		db = m.ApplyScopeFilter(db, locationScope, "loc.id")
		db = m.ApplyScopeFilter(db, areaScope, "a.id")

		if params.AreaID != nil {
			db = db.Where("a.id = ?", *params.AreaID)
		}
		if params.LocationID != nil {
			db = db.Where("loc.id = ?", *params.LocationID)
		}
		if params.KandangID != nil {
			db = db.Where("k.id = ?", *params.KandangID)
		}
		if params.EmployeeID != nil {
			db = db.Where("dca.employee_id = ?", *params.EmployeeID)
		}
		if params.PhaseID != nil {
			db = db.Where("p.id = ?", *params.PhaseID)
		}
		return db
	}

	buildGroupedQuery := func() *gorm.DB {
		return buildBase().
			Select(`
				a.id AS area_id,
				a.name AS area_name,
				loc.id AS location_id,
				loc.name AS location_name,
				k.id AS kandang_id,
				k.name AS kandang_name,
				e.id AS employee_id,
				e.name AS employee_name,
				p.id AS phase_id,
				p.name AS phase_name,
				SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed_assignments,
				COUNT(*) AS total_assignments`).
			Group("a.id, a.name, loc.id, loc.name, k.id, k.name, e.id, e.name, p.id, p.name")
	}

	var total int64
	groupedForCount := buildGroupedQuery()
	if err := s.Repository.DB().WithContext(c.Context()).
		Table("(?) AS grouped", groupedForCount).
		Count(&total).Error; err != nil {
		s.Log.Errorf("Failed to count report data: %+v", err)
		return nil, 0, err
	}

	type reportRow struct {
		AreaID               uint
		AreaName             string
		LocationID           uint
		LocationName         string
		KandangID            uint
		KandangName          string
		EmployeeID           uint
		EmployeeName         string
		PhaseID              uint
		PhaseName            string
		CompletedAssignments int64
		TotalAssignments     int64
	}

	rows := make([]reportRow, 0)
	if err := buildGroupedQuery().
		Order("a.name, loc.name, k.name, e.name").
		Offset(offset).
		Limit(params.Limit).
		Scan(&rows).Error; err != nil {
		s.Log.Errorf("Failed to fetch report data: %+v", err)
		return nil, 0, err
	}

	if len(rows) == 0 {
		return []DailyChecklistReportItem{}, total, nil
	}

	type comboKey struct {
		EmployeeID uint
		KandangID  uint
		PhaseID    uint
	}

	type dailyActivityStat struct {
		Completed int
		Total     int
		Date      time.Time
	}

	employeeIDs := make([]uint, 0)
	kandangIDs := make([]uint, 0)
	phaseIDs := make([]uint, 0)
	comboSet := make(map[comboKey]struct{})
	employeeSet := make(map[uint]struct{})
	kandangSet := make(map[uint]struct{})
	phaseSet := make(map[uint]struct{})

	for _, row := range rows {
		key := comboKey{EmployeeID: row.EmployeeID, KandangID: row.KandangID, PhaseID: row.PhaseID}
		comboSet[key] = struct{}{}
		if _, ok := employeeSet[row.EmployeeID]; !ok {
			employeeSet[row.EmployeeID] = struct{}{}
			employeeIDs = append(employeeIDs, row.EmployeeID)
		}
		if _, ok := kandangSet[row.KandangID]; !ok {
			kandangSet[row.KandangID] = struct{}{}
			kandangIDs = append(kandangIDs, row.KandangID)
		}
		if _, ok := phaseSet[row.PhaseID]; !ok {
			phaseSet[row.PhaseID] = struct{}{}
			phaseIDs = append(phaseIDs, row.PhaseID)
		}
	}

	dailyActivityMap := make(map[comboKey]map[string]dailyActivityStat)
	if len(employeeIDs) > 0 {
		var dailyRows []struct {
			EmployeeID uint
			KandangID  uint
			PhaseID    uint
			Date       time.Time
			Completed  int64
			Total      int64
		}

		dailyQuery := buildBase().
			Where("dca.employee_id IN ?", employeeIDs).
			Where("dc.kandang_id IN ?", kandangIDs).
			Where("dcat.phase_id IN ?", phaseIDs).
			Select(`
			dca.employee_id,
			dc.kandang_id,
			dcat.phase_id,
			dc.date,
			SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed,
			COUNT(*) AS total`).
			Group("dca.employee_id, dc.kandang_id, dcat.phase_id, dc.date")

		if err := dailyQuery.Scan(&dailyRows).Error; err != nil {
			s.Log.Errorf("Failed to fetch daily activities for report: %+v", err)
			return nil, 0, err
		}

		for _, row := range dailyRows {
			key := comboKey{EmployeeID: row.EmployeeID, KandangID: row.KandangID, PhaseID: row.PhaseID}
			if _, ok := comboSet[key]; !ok {
				continue
			}
			if _, ok := dailyActivityMap[key]; !ok {
				dailyActivityMap[key] = make(map[string]dailyActivityStat)
			}
			day := strconv.Itoa(row.Date.Day())
			dailyActivityMap[key][day] = dailyActivityStat{
				Completed: int(row.Completed),
				Total:     int(row.Total),
				Date:      row.Date,
			}
		}
	}

	employeeStats := make(map[uint]struct {
		Completed int64
		Total     int64
	})
	var employeeRows []struct {
		EmployeeID uint
		Completed  int64
		Total      int64
	}
	if err := buildBase().
		Select(`
			dca.employee_id,
			SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed,
			COUNT(*) AS total`).
		Group("dca.employee_id").
		Scan(&employeeRows).Error; err != nil {
		s.Log.Errorf("Failed to fetch employee stats for report: %+v", err)
		return nil, 0, err
	}
	for _, row := range employeeRows {
		employeeStats[row.EmployeeID] = struct {
			Completed int64
			Total     int64
		}{Completed: row.Completed, Total: row.Total}
	}

	kandangStats := make(map[uint]struct {
		Completed int64
		Total     int64
	})
	var kandangRows []struct {
		KandangID uint
		Completed int64
		Total     int64
	}
	if err := buildBase().
		Select(`
			dc.kandang_id,
			SUM(CASE WHEN dca.checked THEN 1 ELSE 0 END) AS completed,
			COUNT(*) AS total`).
		Group("dc.kandang_id").
		Scan(&kandangRows).Error; err != nil {
		s.Log.Errorf("Failed to fetch kandang stats for report: %+v", err)
		return nil, 0, err
	}
	for _, row := range kandangRows {
		kandangStats[row.KandangID] = struct {
			Completed int64
			Total     int64
		}{Completed: row.Completed, Total: row.Total}
	}

	var configs []entity.ConfigChecklist
	if err := s.Repository.DB().WithContext(c.Context()).
		Order("date ASC").
		Find(&configs).Error; err != nil {
		s.Log.Errorf("Failed to load config checklists: %+v", err)
		return nil, 0, err
	}

	getConfigForDate := func(date time.Time) *entity.ConfigChecklist {
		var selected *entity.ConfigChecklist
		for i := range configs {
			if !configs[i].Date.After(date) {
				selected = &configs[i]
			} else {
				break
			}
		}
		if selected == nil {
			return &entity.ConfigChecklist{
				PercentageThresholdBad:    50,
				PercentageThresholdEnough: 75,
			}
		}
		return selected
	}

	items := make([]DailyChecklistReportItem, len(rows))
	for i, row := range rows {
		key := comboKey{EmployeeID: row.EmployeeID, KandangID: row.KandangID, PhaseID: row.PhaseID}

		activities := dailyActivityMap[key]
		if activities == nil {
			activities = map[string]dailyActivityStat{}
		}

		totalChecklist := 0
		categoryCounts := DailyChecklistReportCategory{}
		activityOutput := make(map[string]int, len(activities))

		for day, stat := range activities {
			activityOutput[day] = stat.Completed
			totalChecklist += stat.Completed

			if stat.Total == 0 {
				continue
			}

			cfg := getConfigForDate(stat.Date)
			if cfg == nil {
				continue
			}

			progress := int(math.Ceil(float64(stat.Completed) / float64(stat.Total) * 100))
			if progress <= cfg.PercentageThresholdBad {
				categoryCounts.Kurang++
			} else if progress <= cfg.PercentageThresholdEnough {
				categoryCounts.Cukup++
			} else {
				categoryCounts.Baik++
			}
		}

		employeeStat := employeeStats[row.EmployeeID]
		abkPercentage := 0
		if employeeStat.Total > 0 {
			abkPercentage = int(math.Round(float64(employeeStat.Completed) / float64(employeeStat.Total) * 100))
		}

		kandangStat := kandangStats[row.KandangID]
		kandangPercentage := 0
		if kandangStat.Total > 0 {
			kandangPercentage = int(math.Round(float64(kandangStat.Completed) / float64(kandangStat.Total) * 100))
		}

		items[i] = DailyChecklistReportItem{
			AreaID:          row.AreaID,
			AreaName:        row.AreaName,
			LocationID:      row.LocationID,
			LocationName:    row.LocationName,
			KandangID:       row.KandangID,
			KandangName:     row.KandangName,
			EmployeeID:      row.EmployeeID,
			EmployeeName:    row.EmployeeName,
			PhaseName:       row.PhaseName,
			DailyActivities: activityOutput,
			Summary: DailyChecklistReportSummary{
				TotalChecklist:    totalChecklist,
				JumlahHariEfektif: len(activities),
				AbkPercentage:     abkPercentage,
				KandangPercentage: kandangPercentage,
				Category:          categoryCounts,
			},
		}
	}

	return items, total, nil
}
