package service

import (
	"context"
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
	repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
	expenseRealizationRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
	marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
	marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
	productionStandardRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
	chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
	projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
	recordingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
	purchaseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

type activeKandangMetric struct {
	ProjectFlockKandangID uint
	ProjectFlockID        uint
	KandangID             uint
	Category              string
	Metric                float64
}

type ClosingService interface {
	GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.ClosingListItemDTO, int64, error)
	GetProjectFlockByID(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
	GetPenjualan(ctx *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) ([]entity.MarketingDeliveryProduct, error)
	GetClosingSummary(ctx *fiber.Ctx, projectFlockID uint, kandangID *uint) (any, error)
	GetClosingDataProduksi(ctx *fiber.Ctx, projectFlockID uint, kandangID *uint) (*dto.ClosingProductionReportDTO, error)
	GetOverhead(ctx *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.OverheadListDTO, error)
	GetClosingSapronak(ctx *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error)
	GetClosingSapronakSummary(ctx *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakSummaryItemDTO, error)
	GetExpeditionHPP(ctx *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error)
}

type closingService struct {
	Log                          *logrus.Logger
	Validate                     *validator.Validate
	Repository                   repository.ClosingRepository
	ProjectFlockRepo             projectflockRepository.ProjectflockRepository
	ProjectFlockKandangRepo      projectflockRepository.ProjectFlockKandangRepository
	MarketingRepo                marketingRepository.MarketingRepository
	MarketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository
	ApprovalSvc                  commonSvc.ApprovalService
	ExpenseRealizationRepo       expenseRealizationRepository.ExpenseRealizationRepository
	ProjectBudgetRepo            projectflockRepository.ProjectBudgetRepository
	ChickinRepo                  chickinRepository.ProjectChickinRepository
	PurchaseRepo                 purchaseRepository.PurchaseRepository
	RecordingRepo                recordingRepository.RecordingRepository
	StandardGrowthDetailRepo     productionStandardRepository.StandardGrowthDetailRepository
	ProductionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository
}

func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, projectFlockKandangRepo projectflockRepository.ProjectFlockKandangRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, purchaseRepo purchaseRepository.PurchaseRepository, recordingRepo recordingRepository.RecordingRepository, standardGrowthDetailRepo productionStandardRepository.StandardGrowthDetailRepository, productionStandardDetailRepo productionStandardRepository.ProductionStandardDetailRepository, validate *validator.Validate) ClosingService {
	return &closingService{
		Log:                          utils.Log,
		Validate:                     validate,
		Repository:                   repo,
		ProjectFlockRepo:             projectFlockRepo,
		ProjectFlockKandangRepo:      projectFlockKandangRepo,
		MarketingRepo:                marketingRepo,
		MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
		ApprovalSvc:                  approvalSvc,
		ExpenseRealizationRepo:       expenseRealizationRepo,
		ProjectBudgetRepo:            projectBudgetRepo,
		ChickinRepo:                  chickinRepo,
		PurchaseRepo:                 purchaseRepo,
		RecordingRepo:                recordingRepo,
		StandardGrowthDetailRepo:     standardGrowthDetailRepo,
		ProductionStandardDetailRepo: productionStandardDetailRepo,
	}
}

func (s closingService) withRelations(db *gorm.DB) *gorm.DB {
	return db.Preload("CreatedUser")
}

func (s closingService) withClosingRelations(db *gorm.DB) *gorm.DB {
	return s.withRelations(db).
		Preload("Location").
		Preload("KandangHistory").
		Preload("KandangHistory.Chickins")
}

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

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

	offset := (params.Page - 1) * params.Limit
	statusFilter := ""
	if params.ProjectStatus != nil {
		switch *params.ProjectStatus {
		case 1:
			statusFilter = "Pengajuan"
		case 2:
			statusFilter = "Aktif"
		}
	}

	closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
		db = s.withClosingRelations(db)
		if scope.Restrict {
			if len(scope.IDs) == 0 {
				return db.Where("1 = 0")
			}
			db = m.ApplyScopeFilter(db, scope, "project_flocks.location_id")
		}
		if params.LocationID != nil {
			db = db.Where("location_id = ?", *params.LocationID)
		}
		if statusFilter != "" {
			latestApprovalSubQuery := s.Repository.DB().
				WithContext(c.Context()).
				Table("approvals").
				Select("DISTINCT ON (approvable_id) approvable_id, step_name, id").
				Where("approvable_type = ?", utils.ApprovalWorkflowProjectFlock.String()).
				Order("approvable_id, id DESC")
			db = db.Joins("JOIN (?) AS latest_approval ON latest_approval.approvable_id = project_flocks.id", latestApprovalSubQuery).
				Where("LOWER(latest_approval.step_name) = LOWER(?)", statusFilter)
		}
		if params.Search != "" {
			return db.Where("flock_name ILIKE ?", "%"+params.Search+"%")
		}
		return db.Order("created_at DESC").Order("updated_at DESC")
	})

	if err != nil {
		s.Log.Errorf("Failed to get closings: %+v", err)
		return nil, 0, err
	}

	result := make([]dto.ClosingListItemDTO, 0, len(closings))
	for _, closing := range closings {
		statusProject, _, err := s.getApprovalStatuses(c.Context(), closing.Id)
		if err != nil {
			s.Log.Errorf("Failed to retrieve approval statuses for project flock %d: %+v", closing.Id, err)
			return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch approval status")
		}

		result = append(result, dto.ToClosingListItemDTO(closing, statusProject))
	}

	return result, total, nil
}

func (s closingService) GetProjectFlockByID(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
	if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), id); err != nil {
		return nil, err
	}

	projectFlock, err := s.ProjectFlockRepo.GetByID(c.Context(), id, s.withRelations)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock not found")
	}
	if err != nil {
		return nil, err
	}
	return projectFlock, nil
}

func (s closingService) GetPenjualan(c *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) ([]entity.MarketingDeliveryProduct, error) {
	if projectFlockKandangID != nil {
		if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), projectFlockID, *projectFlockKandangID); err != nil {
			return nil, err
		}
	} else if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil {
		return nil, err
	}

	realisasi, err := s.MarketingDeliveryProductRepo.GetClosingPenjualan(c.Context(), projectFlockID, projectFlockKandangID)
	if err != nil {
		return nil, err
	}
	if len(realisasi) == 0 {
		return []entity.MarketingDeliveryProduct{}, nil
	}

	return realisasi, nil
}

func (s closingService) GetClosingSummary(c *fiber.Ctx, projectFlockID uint, kandangID *uint) (any, error) {
	if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil {
		return nil, err
	}

	if kandangID != nil {
		return s.getClosingSummaryByKandang(c.Context(), projectFlockID, *kandangID)
	}

	project, err := s.Repository.GetByID(c.Context(), projectFlockID, s.withClosingRelations)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found")
	}
	if err != nil {
		s.Log.Errorf("Failed get project flock %d for closing summary: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
	}

	statusProject, statusClosing, err := s.getApprovalStatuses(c.Context(), projectFlockID)
	if err != nil {
		s.Log.Errorf("Failed to retrieve approval statuses for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch approval status")
	}

	summary := dto.ToClosingSummaryDTO(*project, statusProject, statusClosing)

	return &summary, nil
}

func (s closingService) getClosingSummaryByKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*dto.ClosingSummaryKandangDTO, error) {
	if projectFlockID == 0 || kandangID == 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id or kandang id")
	}

	db := s.Repository.DB().WithContext(ctx)

	var kandang entity.ProjectFlockKandang
	if err := db.
		Preload("Kandang").
		Preload("Kandang.Location").
		Preload("Kandang.Pic").
		Where("project_flock_id = ?", projectFlockID).
		Where("kandang_id = ?", kandangID).
		First(&kandang).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "Project flock kandang not found")
		}
		s.Log.Errorf("Failed get project flock kandang %d/%d: %+v", projectFlockID, kandangID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
	}

	var project entity.ProjectFlock
	if err := db.
		Select("id", "category").
		First(&project, projectFlockID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found")
		}
		s.Log.Errorf("Failed get project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
	}

	var population float64
	if err := db.
		Table("project_flock_populations pfp").
		Joins("JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
		Where("pc.project_flock_kandang_id = ?", kandang.Id).
		Select("COALESCE(SUM(pfp.total_qty), 0)").
		Scan(&population).Error; err != nil {
		s.Log.Errorf("Failed to sum population for project flock kandang %d: %+v", kandang.Id, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch population data")
	}

	var chickInDate time.Time
	if err := db.
		Table("project_chickins").
		Where("project_flock_kandang_id = ?", kandang.Id).
		Select("MIN(chick_in_date)").
		Scan(&chickInDate).Error; err != nil {
		s.Log.Errorf("Failed to fetch chick in date for project flock kandang %d: %+v", kandang.Id, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chick in date")
	}

	statusProject := "Belum Selesai"
	var approvalDate string
	if s.ApprovalSvc != nil {
		records, _, err := s.ApprovalSvc.List(ctx, utils.ApprovalWorkflowProjectFlockKandang.String(), &kandang.Id, 1, 1000, "", "")
		if err != nil {
			s.Log.Errorf("Failed to fetch approvals for project flock kandang %d: %+v", kandang.Id, err)
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch approval data")
		}

		var (
			minStep        uint16
			latestActionAt time.Time
		)

		for _, rec := range records {
			if minStep == 0 || rec.StepNumber < minStep {
				minStep = rec.StepNumber
			}

			if latestActionAt.IsZero() || rec.ActionAt.After(latestActionAt) {
				latestActionAt = rec.ActionAt
				statusProject = rec.StepName
			}
		}

		if statusProject == "" && minStep > 0 {
			if label, ok := approvalutils.ApprovalStepName(utils.ApprovalWorkflowProjectFlockKandang, approvalutils.ApprovalStep(minStep)); ok {
				statusProject = label
			}
		}

		if !latestActionAt.IsZero() {
			approvalDate = latestActionAt.Format("2006-01-02")
		}
	}

	closingDate := ""
	if kandang.ClosedAt != nil {
		closingDate = kandang.ClosedAt.Format("2006-01-02")
	}

	chickInDateStr := ""
	if !chickInDate.IsZero() {
		chickInDateStr = chickInDate.Format("2006-01-02")
	}

	populationInt := int(population)

	return &dto.ClosingSummaryKandangDTO{
		FlockID:             projectFlockID,
		Period:              kandang.Period,
		LocationName:        kandang.Kandang.Location.Name,
		Population:          populationInt,
		PopulationFormatted: fmt.Sprintf("%d Ekor", populationInt),
		ProjectType:         project.Category,
		ClosingDate:         closingDate,
		KandangName:         kandang.Kandang.Name,
		ChickInDate:         chickInDateStr,
		PicName:             kandang.Kandang.Pic.Name,
		ApprovalDate:        approvalDate,
		ProjectStatus:       statusProject,
	}, nil
}

func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error) {
	if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil {
		return nil, 0, err
	}

	if params == nil {
		params = &validation.ClosingSapronakQuery{}
	}

	if params.Page == 0 {
		params.Page = 1
	}
	if params.Limit == 0 {
		params.Limit = 10
	}

	if err := s.Validate.Struct(params); err != nil {
		return nil, 0, fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	if params.Type != validation.SapronakTypeIncoming && params.Type != validation.SapronakTypeOutgoing {
		return nil, 0, fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
	}

	warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID, params.KandangID)
	if err != nil {
		s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err)
		return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouses for project flock")
	}

	var projectFlockKandangIDs []uint
	if params.KandangID != nil && *params.KandangID > 0 {
		projectFlockKandangIDs = []uint{*params.KandangID}
	} else {
		projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
		if err != nil {
			s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err)
			return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
		}
	}

	offset := (params.Page - 1) * params.Limit
	startDate, endDate, err := s.getSapronakDateRange(c.Context(), projectFlockID, params.KandangID)
	if err != nil {
		s.Log.Errorf("Failed to resolve sapronak date range for project flock %d: %+v", projectFlockID, err)
		return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve sapronak date range")
	}
	rows, totalResults, err := s.Repository.GetSapronak(c.Context(), repository.SapronakQueryParams{
		Type:                   params.Type,
		WarehouseIDs:           warehouseIDs,
		ProjectFlockKandangIDs: projectFlockKandangIDs,
		Limit:                  params.Limit,
		Offset:                 offset,
		Search:                 params.Search,
		StartDate:              startDate,
		EndDate:                endDate,
	})
	if err != nil {
		s.Log.Errorf("Failed to fetch sapronak %s for project flock %d: %+v", params.Type, projectFlockID, err)
		return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sapronak data")
	}

	items := make([]dto.ClosingSapronakItemDTO, 0, len(rows))
	for _, row := range rows {
		dateStr := row.DateText
		if dateStr == "" && !row.SortDate.IsZero() {
			dateStr = row.SortDate.Format("02-Jan-2006")
		}
		items = append(items, dto.ClosingSapronakItemDTO{
			Id:                   row.Id,
			Date:                 dateStr,
			ReferenceNumber:      row.ReferenceNumber,
			TransactionType:      row.TransactionType,
			ProductName:          row.ProductName,
			ProductCategory:      row.ProductCategory,
			ProductSubCategory:   row.ProductSubCategory,
			SourceWarehouse:      row.SourceWarehouse,
			DestinationWarehouse: row.DestinationWarehouse,
			// Destination:          row.Destination,
			Quantity:          row.Quantity,
			Unit:              row.Unit,
			FormattedQuantity: formatQuantity(row.Quantity, row.Unit),
			Notes:             row.Notes,
			SortDate:          row.SortDate,
		})
	}

	return items, totalResults, nil
}

func (s closingService) GetClosingSapronakSummary(c *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakSummaryItemDTO, error) {
	if projectFlockID == 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
	}

	if params == nil {
		params = &validation.ClosingSapronakQuery{}
	}

	if err := s.Validate.Struct(params); err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	if params.Type != validation.SapronakTypeIncoming && params.Type != validation.SapronakTypeOutgoing {
		return nil, fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
	}

	if _, err := s.Repository.GetByID(c.Context(), projectFlockID, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "Project flock tidak ditemukan")
		}
		s.Log.Errorf("Failed get project flock %d for sapronak closing summary: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
	}

	warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID, params.KandangID)
	if err != nil {
		s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouses for project flock")
	}

	var projectFlockKandangIDs []uint
	if params.KandangID != nil && *params.KandangID > 0 {
		projectFlockKandangIDs = []uint{*params.KandangID}
	} else {
		projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
		if err != nil {
			s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err)
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
		}
	}

	startDate, endDate, err := s.getSapronakDateRange(c.Context(), projectFlockID, params.KandangID)
	if err != nil {
		s.Log.Errorf("Failed to resolve sapronak date range for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to resolve sapronak date range")
	}

	rows, err := s.Repository.GetSapronakSummary(c.Context(), repository.SapronakQueryParams{
		Type:                   params.Type,
		WarehouseIDs:           warehouseIDs,
		ProjectFlockKandangIDs: projectFlockKandangIDs,
		Search:                 params.Search,
		StartDate:              startDate,
		EndDate:                endDate,
	})
	if err != nil {
		s.Log.Errorf("Failed to fetch sapronak %s summary for project flock %d: %+v", params.Type, projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sapronak summary data")
	}

	items := make([]dto.ClosingSapronakSummaryItemDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, dto.ClosingSapronakSummaryItemDTO{
			Category: row.Category,
			TotalQty: row.TotalQty,
			Uom: dto.UomSummaryDTO{
				ID:   row.UomID,
				Name: row.UomName,
			},
		})
	}

	return items, nil
}

func (s closingService) getWarehouseIDsByProjectFlock(ctx context.Context, projectFlockID uint, kandangID *uint) ([]uint, error) {
	var kandangIDs []uint
	db := s.Repository.DB().WithContext(ctx)

	query := db.Model(&entity.ProjectFlockKandang{}).
		Where("project_flock_id = ?", projectFlockID)
	if kandangID != nil && *kandangID > 0 {
		query = query.Where("id = ?", *kandangID)
	}
	if err := query.Pluck("kandang_id", &kandangIDs).Error; err != nil {
		return nil, err
	}

	if len(kandangIDs) == 0 {
		return []uint{}, nil
	}

	var warehouses []entity.Warehouse
	if err := db.Where("kandang_id IN ?", kandangIDs).Find(&warehouses).Error; err != nil {
		return nil, err
	}

	unique := make(map[uint]struct{})
	for _, warehouse := range warehouses {
		unique[warehouse.Id] = struct{}{}
	}

	ids := make([]uint, 0, len(unique))
	for id := range unique {
		ids = append(ids, id)
	}

	return ids, nil
}

func (s closingService) getProjectFlockKandangIDs(ctx context.Context, projectFlockID uint) ([]uint, error) {
	var ids []uint
	query := s.Repository.DB().WithContext(ctx).
		Model(&entity.ProjectFlockKandang{}).
		Where("project_flock_id = ?", projectFlockID)
	err := query.Order("id ASC").Pluck("id", &ids).Error
	if err != nil {
		return nil, err
	}

	return ids, nil
}

func (s closingService) getSapronakDateRange(ctx context.Context, projectFlockID uint, kandangID *uint) (*time.Time, *time.Time, error) {
	db := s.Repository.DB().WithContext(ctx)

	if kandangID != nil && *kandangID > 0 {
		var pfk entity.ProjectFlockKandang
		if err := db.Select("id, created_at, closed_at").First(&pfk, *kandangID).Error; err != nil {
			return nil, nil, err
		}

		var firstChickin struct {
			ChickInDate time.Time `gorm:"column:chick_in_date"`
		}
		if err := db.Table("project_chickins").
			Select("chick_in_date").
			Where("project_flock_kandang_id = ? AND chick_in_date IS NOT NULL", pfk.Id).
			Order("chick_in_date ASC").
			Limit(1).
			Scan(&firstChickin).Error; err != nil {
			return nil, nil, err
		}

		start := pfk.CreatedAt
		if !firstChickin.ChickInDate.IsZero() {
			start = firstChickin.ChickInDate
		}
		startDate := dateOnlyUTC(start)

		var endDate *time.Time
		if pfk.ClosedAt != nil {
			d := dateOnlyUTC(*pfk.ClosedAt)
			endDate = &d
		}

		return &startDate, endDate, nil
	}

	var firstPFK entity.ProjectFlockKandang
	if err := db.Model(&entity.ProjectFlockKandang{}).
		Select("created_at").
		Where("project_flock_id = ?", projectFlockID).
		Order("created_at ASC").
		First(&firstPFK).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil, nil
		}
		return nil, nil, err
	}

	var firstChickin struct {
		ChickInDate time.Time `gorm:"column:chick_in_date"`
	}
	if err := db.Table("project_chickins pc").
		Select("pc.chick_in_date").
		Joins("JOIN project_flock_kandangs pfk ON pfk.id = pc.project_flock_kandang_id").
		Where("pfk.project_flock_id = ? AND pc.chick_in_date IS NOT NULL", projectFlockID).
		Order("pc.chick_in_date ASC").
		Limit(1).
		Scan(&firstChickin).Error; err != nil {
		return nil, nil, err
	}

	start := firstPFK.CreatedAt
	if !firstChickin.ChickInDate.IsZero() {
		start = firstChickin.ChickInDate
	}
	startDate := dateOnlyUTC(start)

	var endDate *time.Time
	var openCount int64
	if err := db.Model(&entity.ProjectFlockKandang{}).
		Where("project_flock_id = ? AND closed_at IS NULL", projectFlockID).
		Count(&openCount).Error; err != nil {
		return nil, nil, err
	}
	if openCount == 0 {
		var latestClosed entity.ProjectFlockKandang
		if err := db.Model(&entity.ProjectFlockKandang{}).
			Select("closed_at").
			Where("project_flock_id = ? AND closed_at IS NOT NULL", projectFlockID).
			Order("closed_at DESC").
			First(&latestClosed).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return &startDate, nil, nil
			}
			return nil, nil, err
		}
		if latestClosed.ClosedAt != nil && !latestClosed.ClosedAt.IsZero() {
			d := dateOnlyUTC(*latestClosed.ClosedAt)
			endDate = &d
		}
	}

	return &startDate, endDate, nil
}

func dateOnlyUTC(t time.Time) time.Time {
	u := t.UTC()
	return time.Date(u.Year(), u.Month(), u.Day(), 0, 0, 0, 0, time.UTC)
}

func formatQuantity(qty float64, uom string) string {
	qtyStr := strconv.FormatFloat(qty, 'f', -1, 64)
	if uom == "" {
		return qtyStr
	}
	return qtyStr + " " + uom
}

func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID uint) (string, string, error) {
	if s.ApprovalSvc == nil {
		return "", "Belum Selesai", nil
	}

	records, _, err := s.ApprovalSvc.List(ctx, utils.ApprovalWorkflowProjectFlock.String(), &projectFlockID, 1, 1000, "", "")
	if err != nil {
		return "", "", err
	}

	var (
		minStep        uint16
		statusProject  string
		completed      int
		latestActionAt time.Time
	)

	for _, rec := range records {
		if minStep == 0 || rec.StepNumber < minStep {
			minStep = rec.StepNumber
		}

		if latestActionAt.IsZero() || rec.ActionAt.After(latestActionAt) {
			latestActionAt = rec.ActionAt
			statusProject = rec.StepName
		}
	}

	if statusProject == "" && minStep > 0 {
		if label, ok := approvalutils.ApprovalStepName(utils.ApprovalWorkflowProjectFlock, approvalutils.ApprovalStep(minStep)); ok {
			statusProject = label
		}
	}

	statusClosing := "Belum Selesai"
	switch {
	case len(records) == 0 || completed == 0:
		statusClosing = "Belum Selesai"
	case completed < len(records):
		statusClosing = "Sebagian"
	default:
		statusClosing = "Selesai"
	}

	return statusProject, statusClosing, nil
}

func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.OverheadListDTO, error) {
	if projectFlockKandangID != nil {
		if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), projectFlockID, *projectFlockKandangID); err != nil {
			return nil, err
		}
	} else if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil {
		return nil, err
	}

	budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
	if err != nil {
		return nil, err
	}

	realizations, err := s.ExpenseRealizationRepo.GetClosingOverhead(c.Context(), projectFlockID, projectFlockKandangID)
	if err != nil {
		return nil, err
	}

	realizations, err = s.allocateFarmOverheadRealizations(c.Context(), projectFlockID, projectFlockKandangID, realizations)
	if err != nil {
		return nil, err
	}

	projectFlockKandangs, err := s.ProjectFlockKandangRepo.GetByProjectFlockID(c.Context(), projectFlockID)
	if err != nil {
		return nil, err
	}
	totalKandangCount := len(projectFlockKandangs)

	chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
	if err != nil {
		return nil, err
	}

	var totalChickinQty float64
	var totalDepletion float64

	if projectFlockKandangID != nil {
		for _, chickin := range chickins {
			if chickin.ProjectFlockKandangId == *projectFlockKandangID {
				totalChickinQty += chickin.UsageQty
			}
		}

		var depletionResult float64
		err = s.RecordingRepo.DB().WithContext(c.Context()).
			Table("recording_depletions").
			Select("COALESCE(SUM(recording_depletions.qty), 0)").
			Joins("JOIN recordings ON recordings.id = recording_depletions.recording_id").
			Where("recordings.project_flock_kandangs_id = ?", *projectFlockKandangID).
			Scan(&depletionResult).Error
		if err != nil {
			s.Log.Warnf("GetTotalDepletionByProjectFlockKandangID error: %v", err)
		} else {
			totalDepletion = depletionResult
		}
	} else {
		for _, chickin := range chickins {
			totalChickinQty += chickin.UsageQty
		}

		totalDepletion, err = s.RecordingRepo.GetTotalDepletionByProjectFlockID(c.Context(), projectFlockID)
		if err != nil {
			s.Log.Warnf("GetTotalDepletionByProjectFlockID error: %v", err)
		}
	}

	totalActualPopulation := totalChickinQty - totalDepletion

	result := dto.ToOverheadListDTOs(budgets, realizations, totalChickinQty, totalActualPopulation, projectFlockKandangID != nil, totalKandangCount)

	return &result, nil
}

type activeKandangMetricRow struct {
	ProjectFlockKandangID uint    `gorm:"column:project_flock_kandang_id"`
	ProjectFlockID        uint    `gorm:"column:project_flock_id"`
	KandangID             uint    `gorm:"column:kandang_id"`
	Category              string  `gorm:"column:category"`
	ChickinQty            float64 `gorm:"column:chickin_qty"`
	DepletionQty          float64 `gorm:"column:depletion_qty"`
	EggQty                float64 `gorm:"column:egg_qty"`
}

func (s closingService) getActiveKandangMetrics(ctx context.Context, locationID uint, transactionDate time.Time) ([]activeKandangMetric, error) {
	db := s.Repository.DB().WithContext(ctx)

	rows := []activeKandangMetricRow{}
	rawSQL := `
SELECT
	pfk.id AS project_flock_kandang_id,
	pfk.project_flock_id AS project_flock_id,
	pfk.kandang_id AS kandang_id,
	pf.category AS category,
	COALESCE((
		SELECT SUM(pc.usage_qty)
		FROM project_chickins pc
		WHERE pc.project_flock_kandang_id = pfk.id
			AND pc.chick_in_date::date <= ?
	), 0) AS chickin_qty,
	COALESCE((
		SELECT SUM(rd.qty)
		FROM recording_depletions rd
		JOIN recordings r ON r.id = rd.recording_id
		WHERE r.project_flock_kandangs_id = pfk.id
			AND r.record_datetime::date <= ?
	), 0) AS depletion_qty,
	COALESCE((
		SELECT SUM(re.qty)
		FROM recording_eggs re
		JOIN recordings r2 ON r2.id = re.recording_id
		WHERE r2.project_flock_kandangs_id = pfk.id
			AND r2.record_datetime::date <= ?
	), 0) AS egg_qty
FROM project_flock_kandangs pfk
JOIN project_flocks pf ON pf.id = pfk.project_flock_id
WHERE pf.location_id = ?
	AND (pfk.closed_at IS NULL OR pfk.closed_at::date > ?)
	AND EXISTS (
		SELECT 1
		FROM project_chickins pc2
		WHERE pc2.project_flock_kandang_id = pfk.id
			AND pc2.chick_in_date::date <= ?
	)
`
	if err := db.Raw(rawSQL, transactionDate, transactionDate, transactionDate, locationID, transactionDate, transactionDate).Scan(&rows).Error; err != nil {
		return nil, err
	}

	result := make([]activeKandangMetric, 0, len(rows))
	for _, row := range rows {
		metric := 0.0
		switch strings.ToLower(strings.TrimSpace(row.Category)) {
		case "growing":
			metric = row.ChickinQty
		case "laying":
			metric = row.EggQty
		default:
			s.Log.Warnf("Unknown project flock category for overhead allocation: %s (pfk=%d)", row.Category, row.ProjectFlockKandangID)
		}

		result = append(result, activeKandangMetric{
			ProjectFlockKandangID: row.ProjectFlockKandangID,
			ProjectFlockID:        row.ProjectFlockID,
			KandangID:             row.KandangID,
			Category:              row.Category,
			Metric:                metric,
		})
	}

	return result, nil
}

func round2(value float64) float64 {
	return math.Round(value*100) / 100
}

func allocateFarmLevelQty(totalQty float64, metrics []activeKandangMetric) map[uint]float64 {
	allocations := make(map[uint]float64, len(metrics))
	if totalQty == 0 || len(metrics) == 0 {
		return allocations
	}

	totalMetric := 0.0
	var maxMetric float64
	var maxMetricID uint
	for _, m := range metrics {
		if m.Metric <= 0 {
			continue
		}
		totalMetric += m.Metric
		if m.Metric > maxMetric || maxMetricID == 0 {
			maxMetric = m.Metric
			maxMetricID = m.ProjectFlockKandangID
		}
	}
	if totalMetric == 0 {
		return allocations
	}

	sumRounded := 0.0
	for _, m := range metrics {
		if m.Metric <= 0 {
			continue
		}
		portion := totalQty * (m.Metric / totalMetric)
		rounded := round2(portion)
		allocations[m.ProjectFlockKandangID] = rounded
		sumRounded += rounded
	}

	diff := totalQty - sumRounded
	if maxMetricID != 0 && diff != 0 {
		allocations[maxMetricID] = round2(allocations[maxMetricID] + diff)
	}

	return allocations
}

func (s closingService) allocateFarmOverheadRealizations(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint, realizations []entity.ExpenseRealization) ([]entity.ExpenseRealization, error) {
	if len(realizations) == 0 {
		return realizations, nil
	}

	cache := make(map[string][]activeKandangMetric)
	allocated := make([]entity.ExpenseRealization, 0, len(realizations))

	for _, realization := range realizations {
		expenseNonstock := realization.ExpenseNonstock
		if expenseNonstock == nil || expenseNonstock.Expense == nil {
			allocated = append(allocated, realization)
			continue
		}

		// If already bound to a specific project flock kandang, don't re-allocate.
		if expenseNonstock.ProjectFlockKandangId != nil {
			allocated = append(allocated, realization)
			continue
		}

		expense := expenseNonstock.Expense
		locationID := uint(expense.LocationId)
		txDate := expense.RealizationDate

		cacheKey := fmt.Sprintf("%d|%s", locationID, txDate.Format("2006-01-02"))
		metrics, exists := cache[cacheKey]
		if !exists {
			var err error
			metrics, err = s.getActiveKandangMetrics(ctx, locationID, txDate)
			if err != nil {
				return nil, err
			}
			cache[cacheKey] = metrics
		}

		allocations := allocateFarmLevelQty(realization.Qty, metrics)
		allocatedQty := 0.0
		if projectFlockKandangID != nil {
			allocatedQty = allocations[*projectFlockKandangID]
		} else {
			for _, m := range metrics {
				if m.ProjectFlockID == projectFlockID {
					allocatedQty += allocations[m.ProjectFlockKandangID]
				}
			}
			allocatedQty = round2(allocatedQty)
		}

		adj := realization
		adj.Qty = allocatedQty
		if adj.Qty == 0 {
			adj.Price = realization.Price
		}

		allocated = append(allocated, adj)
	}

	return allocated, nil
}

func (s closingService) GetExpeditionHPP(c *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error) {
	if projectFlockKandangID != nil {
		if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), projectFlockID, *projectFlockKandangID); err != nil {
			return nil, err
		}
	} else if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil {
		return nil, err
	}

	rows, err := s.Repository.GetExpeditionHPP(c.Context(), projectFlockID, projectFlockKandangID)
	if err != nil {
		s.Log.Errorf("Failed to get expedition HPP for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch expedition HPP")
	}

	expeditionCosts := make([]dto.ExpeditionCostItemDTO, 0, len(rows))
	var totalHPP float64

	for idx, row := range rows {
		expeditionCosts = append(expeditionCosts, dto.ExpeditionCostItemDTO{
			Id:                   uint64(idx + 1),
			ExpeditionVendorName: row.SupplierName,
			HPPAmount:            row.TotalAmount,
		})

		totalHPP += row.TotalAmount
	}

	result := &dto.ExpeditionHPPDTO{
		ExpeditionCosts: expeditionCosts,
		TotalHPPAmount:  totalHPP,
	}

	return result, nil
}

func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint, kandangID *uint) (*dto.ClosingProductionReportDTO, error) {
	if projectFlockID == 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
	}

	var projectFlockKandangIDs []uint
	if kandangID != nil && *kandangID > 0 {
		projectFlockKandangIDs = []uint{*kandangID}
	} else {
		var err error
		projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
		if err != nil {
			s.Log.Errorf("Failed to fetch project flock kandangs for %d: %+v", projectFlockID, err)
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandangs")
		}
	}

	if len(projectFlockKandangIDs) == 0 {
		return nil, fiber.NewError(fiber.StatusNotFound, "No project flock kandang found")
	}

	project, err := s.Repository.GetByID(c.Context(), projectFlockID, s.withRelations)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found")
	}
	if err != nil {
		s.Log.Errorf("Failed get project flock %d for closing data produksi: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
	}

	population, err := s.Repository.SumProjectChickinUsageByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs)
	if err != nil {
		s.Log.Errorf("Failed to sum population for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch population data")
	}

	isGrowing := strings.EqualFold(project.Category, string(utils.ProjectFlockCategoryGrowing))

	currentWeek, err := s.determineProductionWeek(c.Context(), projectFlockKandangIDs)
	if err != nil {
		s.Log.Errorf("Failed to determine production week for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine production week")
	}

	if !isGrowing && currentWeek != 0 {
		currentWeek = currentWeek + 17
	}

	targetAverages, err := s.RecordingRepo.GetAverageTargetMetricsByProjectFlockKandangID(c.Context(), projectFlockKandangIDs[0], !isGrowing)
	if err != nil {
		s.Log.Errorf("Failed to calculate target metrics for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch target metrics data")
	}
	var fcrActFromRecording *float64
	if targetAverages.FcrCount > 0 {
		fcrAvg := targetAverages.FcrAvg
		fcrActFromRecording = &fcrAvg
	}

	feedIn, feedUsed, err := s.Repository.SumFeedPurchaseAndUsedByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs)
	if err != nil {
		s.Log.Errorf("Failed to sum feed purchase/used qty for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch feed purchase data")
	}

	averageFeedIntake := targetAverages.FeedIntakeAvg

	feedIntakeStd := 0.0
	var mortalityStdFromGrowth *float64
	if project.ProductionStandardId > 0 && currentWeek > 0 && s.StandardGrowthDetailRepo != nil {
		growthDetail, growthErr := s.StandardGrowthDetailRepo.GetByStandardIDAndWeek(c.Context(), project.ProductionStandardId, currentWeek)
		if growthErr != nil {
			if !errors.Is(growthErr, gorm.ErrRecordNotFound) {
				s.Log.Errorf("Failed to fetch growth detail for project flock %d: %+v", projectFlockID, growthErr)
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch growth standard data")
			}
		} else if growthDetail != nil {
			if growthDetail.FeedIntake != nil {
				feedIntakeStd = *growthDetail.FeedIntake
			}
			if growthDetail.MaxDepletion != nil {
				mortalityStdFromGrowth = growthDetail.MaxDepletion
			}
		}
	}

	var productionStandardDetail *entity.ProductionStandardDetail
	if project.ProductionStandardId > 0 && currentWeek > 0 && s.ProductionStandardDetailRepo != nil {
		productionStandardDetail, err = s.ProductionStandardDetailRepo.GetByStandardIDAndWeek(c.Context(), project.ProductionStandardId, currentWeek)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				productionStandardDetail = nil
			} else {
				s.Log.Errorf("Failed to fetch production standard detail for project flock %d: %+v", projectFlockID, err)
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch production standard detail data")
			}
		}
	}

	claimCulling, err := s.Repository.SumClaimCullingByProjectFlockKandangIDs(c.Context(), projectFlockKandangIDs)
	if err != nil {
		s.Log.Errorf("Failed to sum claim culling for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch claim culling data")
	}

	finalPopulation := population - claimCulling

	age, err := s.calculateAverageSalesAge(c.Context(), projectFlockID, kandangID)
	if err != nil {
		s.Log.Errorf("Failed to calculate sales age for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales age data")
	}

	// feedUsedPerHead := 0.0
	// if population > 0 {
	// 	feedUsedPerHead = feedUsed / population
	// }

	purchase := dto.ClosingPurchaseDTO{
		InitialPopulation: int(population),
		ClaimCulling:      int(claimCulling),
		FinalPopulation:   int(finalPopulation),
		FeedIn:            feedIn,
		FeedUsed:          feedUsed,
		// FeedUsedPerHead:   feedUsedPerHead,
	}

	chickenFlagNames := []string{string(utils.FlagPullet), string(utils.FlagAyamAfkir), string(utils.FlagAyamCulling), string(utils.FlagLayer)}
	chickenSalesWeight, chickenSalesQty, chickenSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, chickenFlagNames)
	if err != nil {
		s.Log.Errorf("Failed to fetch chicken sales data for project flock %d: %+v", projectFlockID, err)
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chicken sales data")
	}

	var chickenAverageWeight float64
	if chickenSalesQty > 0 {
		chickenAverageWeight = chickenSalesWeight / chickenSalesQty
	}

	var chickenAverageSellingPrice float64
	if chickenSalesWeight > 0 {
		chickenAverageSellingPrice = chickenSalesPrice / chickenSalesWeight
	}

	chickenSales := dto.ClosingSalesDTO{
		SalesPopulation:     int(chickenSalesQty),
		SalesWeight:         chickenSalesWeight,
		AverageWeight:       chickenAverageWeight,
		AverageSellingPrice: chickenAverageSellingPrice,
	}

	chickenDepletion := population - chickenSalesQty
	if chickenDepletion < 0 {
		chickenDepletion = 0
	}

	chickenPerformance := calculatePerformanceMetrics(chickenAverageWeight, chickenSalesWeight, feedUsed, population, chickenDepletion, age)
	if fcrActFromRecording != nil {
		chickenPerformance.FcrAct = *fcrActFromRecording
	}

	var eggSales *dto.ClosingEggSalesDTO
	var eggPerformance *dto.ClosingPerformanceDTO
	if !isGrowing {
		eggFlagNames := []string{
			string(utils.FlagTelur),
			string(utils.FlagTelurUtuh),
			string(utils.FlagTelurPecah),
			string(utils.FlagTelurPutih),
			string(utils.FlagTelurRetak),
		}

		eggSalesWeight, eggSalesQty, eggSalesPrice, err := s.Repository.SumMarketingWeightAndQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, eggFlagNames)
		if err != nil {
			s.Log.Errorf("Failed to fetch egg sales data for project flock %d: %+v", projectFlockID, err)
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch egg sales data")
		}

		var averageEggWeight float64
		if eggSalesQty > 0 {
			averageEggWeight = eggSalesWeight / eggSalesQty
		}

		var averageEggSellingPrice float64
		if eggSalesWeight > 0 {
			averageEggSellingPrice = eggSalesPrice / eggSalesWeight
		}

		eggSales = &dto.ClosingEggSalesDTO{
			EggPieces:           int(eggSalesQty),
			EggMassKg:           eggSalesWeight,
			AverageEggWeightKg:  averageEggWeight,
			AverageSellingPrice: averageEggSellingPrice,
		}

		harvestEggQty, err := s.Repository.SumRecordingEggQtyByProjectFlockKandangIDsAndFlagNames(c.Context(), projectFlockKandangIDs, eggFlagNames)
		if err != nil {
			s.Log.Errorf("Failed to fetch recording egg qty for project flock %d: %+v", projectFlockID, err)
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch egg harvest data")
		}

		eggDepletion := harvestEggQty - eggSalesQty
		if eggDepletion < 0 {
			eggDepletion = 0
		}

		eggPerf := calculatePerformanceMetrics(averageEggWeight, eggSalesWeight, feedUsed, harvestEggQty, eggDepletion, age)
		if fcrActFromRecording != nil {
			eggPerf.FcrAct = *fcrActFromRecording
		}
		eggPerformance = &eggPerf
	}

	sales := dto.ClosingSalesGroupDTO{
		Chicken: chickenSales,
		Egg:     eggSales,
	}

	performance := dto.ClosingPerformanceDTO{
		Depletion:     chickenPerformance.Depletion,
		Age:           age,
		MortalityStd:  chickenPerformance.MortalityStd,
		MortalityAct:  chickenPerformance.MortalityAct,
		DeffMortality: chickenPerformance.DeffMortality,
	}
	if eggPerformance != nil {
		// performance.FcrStd = eggPerformance.FcrStd
		performance.FcrAct = eggPerformance.FcrAct
		// performance.DeffFcr = eggPerformance.DeffFcr
		performance.AwgAct = eggPerformance.AwgAct
	} else {
		// performance.FcrStd = chickenPerformance.FcrStd
		performance.FcrAct = chickenPerformance.FcrAct
		// performance.DeffFcr = chickenPerformance.DeffFcr
		performance.AwgAct = chickenPerformance.AwgAct
	}
	performance.FeedIntake = averageFeedIntake
	performance.FeedIntakeStd = feedIntakeStd
	if targetAverages.CumDepletionRateCount > 0 {
		performance.MortalityAct = targetAverages.CumDepletionRateAvg
		performance.DeffMortality = performance.MortalityAct - performance.MortalityStd
	}
	if mortalityStdFromGrowth != nil {
		performance.MortalityStd = *mortalityStdFromGrowth
		performance.DeffMortality = performance.MortalityAct - performance.MortalityStd
	}
	if !isGrowing {
		if targetAverages.HenDayCount > 0 {
			henDayAct := targetAverages.HenDayAvg
			performance.HenDayAct = henDayAct
		}
		if targetAverages.HenHouseCount > 0 {
			henHouseAct := targetAverages.HenHouseAvg
			performance.HenHouseAct = henHouseAct
		}
		if targetAverages.EggWeightCount > 0 {
			eggWeight := targetAverages.EggWeightAvg
			performance.EggWeight = eggWeight
		}
		if targetAverages.EggMassCount > 0 {
			eggMass := targetAverages.EggMassAvg
			performance.EggMass = eggMass
		}
	}
	if productionStandardDetail != nil {
		if productionStandardDetail.StandardFCR != nil {
			performance.FcrStd = *productionStandardDetail.StandardFCR
			performance.DeffFcr = performance.FcrStd - performance.FcrAct
		}
		if !isGrowing {
			if productionStandardDetail.TargetHenDayProduction != nil {
				performance.HendayStd = *productionStandardDetail.TargetHenDayProduction
			}
			if productionStandardDetail.TargetHenHouseProduction != nil {
				performance.HenHouseStd = *productionStandardDetail.TargetHenHouseProduction
			}
			if productionStandardDetail.TargetEggWeight != nil {
				performance.EggWeightStd = *productionStandardDetail.TargetEggWeight
			}
			if productionStandardDetail.TargetEggMass != nil {
				performance.EggMassStd = *productionStandardDetail.TargetEggMass
			}
		}
	}

	result := dto.ClosingProductionReportDTO{
		Purchase:    purchase,
		Sales:       sales,
		Performance: performance,
	}

	return &result, nil
}

func (s closingService) calculateAverageSalesAge(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) (float64, error) {
	penjualan, err := s.MarketingDeliveryProductRepo.GetClosingPenjualanForAgeChickDataProduction(ctx, projectFlockID, projectFlockKandangID)
	if err != nil {
		return 0, err
	}
	acumulateAgeQty := 0.0
	totalQty := 0.0
	for _, v := range penjualan {
		sale := dto.ToSalesAgeDTO(v)
		acumulateAgeQty += float64(sale.Age) * sale.Qty
		totalQty += sale.Qty
	}
	if totalQty > 0 {
		averageAge := acumulateAgeQty / totalQty
		return averageAge, nil
	}

	return 0, err
}

func (s closingService) determineProductionWeek(ctx context.Context, projectFlockKandangIDs []uint) (int, error) {
	if len(projectFlockKandangIDs) == 0 {
		return 0, nil
	}

	firstKandangID := projectFlockKandangIDs[0]

	var chickin entity.ProjectChickin
	if err := s.Repository.DB().WithContext(ctx).
		Where("project_flock_kandang_id = ?", firstKandangID).
		Order("chick_in_date ASC").
		First(&chickin).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return 0, nil
		}
		return 0, err
	}

	recording, err := s.RecordingRepo.GetLatestByProjectFlockKandangID(ctx, firstKandangID)
	if err != nil {
		return 0, err
	}
	if recording == nil {
		return 0, nil
	}

	if recording.RecordDatetime.Before(chickin.ChickInDate) {
		return 0, nil
	}

	elapsed := recording.RecordDatetime.Sub(chickin.ChickInDate)
	weekFloat := elapsed.Hours() / (24 * 7)
	week := int(math.Ceil(weekFloat))
	if week <= 0 {
		week = 1
	}

	return week, nil
}

func calculatePerformanceMetrics(averageWeight, totalWeight, feedUsed, basePopulation, depletion, age float64) dto.ClosingPerformanceDTO {
	mortalityStd, fcrStd := 0.0, 0.0

	fcrAct := 0.0
	if totalWeight > 0 {
		fcrAct = feedUsed / totalWeight
	}

	mortalityAct := 0.0
	if basePopulation > 0 {
		mortalityAct = (depletion / basePopulation) * 100
	}

	deffMortality := mortalityAct - mortalityStd
	deffFcr := fcrAct - fcrStd

	awg := 0.0
	if age > 0 {
		awg = averageWeight / age
	}

	return dto.ClosingPerformanceDTO{
		Depletion:     depletion,
		Age:           age,
		MortalityStd:  mortalityStd,
		MortalityAct:  mortalityAct,
		DeffMortality: deffMortality,
		FcrStd:        fcrStd,
		FcrAct:        fcrAct,
		DeffFcr:       deffFcr,
		AwgAct:        awg,
	}
}
