package repository

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gitlab.com/mbugroup/lti-api.git/internal/config"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	"gorm.io/gorm"
)

type SellingPriceAggregate struct {
	TotalPrice  float64
	TotalWeight float64
}

type FeedUsageByUom struct {
	TotalQty float64
	UomName  string
}

type RecordingWeeklyMetric struct {
	Week             int
	HenDay           float64
	EggWeight        float64
	FeedIntake       float64
	FcrValue         float64
	CumDepletionRate float64
}

type UniformityWeeklyMetric struct {
	Week          int
	Uniformity    float64
	AverageWeight float64
	UniformDate   time.Time
}

type StandardWeeklyMetric struct {
	Week          int
	StdLaying     float64
	StdEggWeight  float64
	StdFeedIntake float64
	StdUniformity float64
	StdDepletion  float64
	StdBodyWeight float64
}

type StandardWeeklyFcrMetric struct {
	Week   int
	StdFcr float64
}

type ComparisonSeries struct {
	Id    uint
	Label string
}

type ComparisonWeeklyMetric struct {
	Week     int
	SeriesId uint
	Value    float64
}

type ComparisonUniformityMetric struct {
	Week          int
	SeriesId      uint
	Uniformity    float64
	AverageWeight float64
}

type EggQualityWeeklyMetric struct {
	Week        int
	NormalQty   float64
	AbnormalQty float64
	TotalQty    float64
}

type WeeklyEggWeightMetric struct {
	Week           int
	EggWeightGrams float64
}

type WeeklyFeedUsageMetric struct {
	Week     int
	TotalQty float64
	UomName  string
}

func applyDashboardFilters(db *gorm.DB, filters *validation.DashboardFilter) *gorm.DB {
	if filters == nil {
		return db
	}
	if len(filters.FlockIds) > 0 {
		db = db.Where("pfk.project_flock_id IN ?", filters.FlockIds)
	}
	if len(filters.KandangIds) > 0 {
		db = db.Where("k.id IN ?", filters.KandangIds)
	}
	if len(filters.LokasiIds) > 0 {
		db = db.Where("k.location_id IN ?", filters.LokasiIds)
	}
	return db
}

func dashboardUniformityWeekExpr() string {
	return fmt.Sprintf(`CASE
		WHEN u.uniform_date IS NULL OR pc.chick_in_date IS NULL THEN 0
		WHEN u.uniform_date::date < pc.chick_in_date THEN 0
		WHEN UPPER(pf.category) = 'LAYING' THEN (((u.uniform_date::date - pc.chick_in_date)::int) / 7) + %d
		ELSE (((u.uniform_date::date - pc.chick_in_date)::int) / 7) + 1
	END`, config.LayingWeekStart())
}

func (r *DashboardRepositoryImpl) GetRecordingWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]RecordingWeeklyMetric, error) {
	var rows []RecordingWeeklyMetric

	weekExpr := `CASE
		WHEN r.day IS NULL OR r.day <= 0 THEN 1
		WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17
		ELSE ((r.day - 1) / 7 + 1)
	END`

	db := r.DB().WithContext(ctx).
		Table("recordings AS r").
		Select(fmt.Sprintf(`%s AS week,
			COALESCE(AVG(r.hen_day), 0) AS hen_day,
			COALESCE(AVG(r.egg_weight), 0) AS egg_weight,
			COALESCE(AVG(r.feed_intake), 0) AS feed_intake,
			COALESCE(AVG(r.fcr_value), 0) AS fcr_value,
			COALESCE(AVG(r.cum_depletion_rate), 0) AS cum_depletion_rate`, weekExpr)).
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL").
		Where("r.day IS NOT NULL AND r.day > 0")

	db = applyDashboardFilters(db, filters)

	if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetUniformityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]UniformityWeeklyMetric, error) {
	var rows []UniformityWeeklyMetric
	weekExpr := dashboardUniformityWeekExpr()

	db := r.DB().WithContext(ctx).
		Table("project_flock_kandang_uniformity AS u").
		Select(fmt.Sprintf(`%s AS week,
			COALESCE(AVG(u.uniformity), 0) AS uniformity,
			COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`, weekExpr)).
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins(`JOIN (
			SELECT project_flock_kandang_id, MIN(chick_in_date)::date AS chick_in_date
			FROM project_chickins
			WHERE deleted_at IS NULL
			GROUP BY project_flock_kandang_id
		) AS pc ON pc.project_flock_kandang_id = u.project_flock_kandang_id`).
		Where("u.uniform_date IS NOT NULL").
		Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end).
		Where("u.uniform_date::date >= pc.chick_in_date")

	db = applyDashboardFilters(db, filters)

	if err := db.Group(weekExpr).Order("1 ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetStandardWeeklyMetrics(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyMetric, error) {
	if len(weeks) == 0 {
		return nil, nil
	}

	standardIDs := r.standardIDSubquery(filters)
	if standardIDs == nil {
		return nil, nil
	}

	var rows []StandardWeeklyMetric
	db := r.DB().WithContext(ctx).
		Table("standard_growth_details AS sgd").
		Select(`sgd.week AS week,
			COALESCE(AVG(psd.target_hen_day_production), 0) AS std_laying,
			COALESCE(AVG(psd.target_egg_weight), 0) AS std_egg_weight,
			COALESCE(AVG(sgd.feed_intake), 0) AS std_feed_intake,
			COALESCE(AVG(sgd.min_uniformity), 0) AS std_uniformity,
			COALESCE(AVG(sgd.max_depletion), 0) AS std_depletion,
			COALESCE(AVG(sgd.target_mean_bw), 0) AS std_body_weight`).
		Joins("LEFT JOIN production_standard_details AS psd ON psd.production_standard_id = sgd.production_standard_id AND psd.week = sgd.week").
		Where("sgd.week IN ?", weeks).
		Where("sgd.production_standard_id IN (?)", standardIDs)

	if err := db.Group("sgd.week").Order("sgd.week ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetStandardFcrWeekly(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyFcrMetric, error) {
	if len(weeks) == 0 {
		return nil, nil
	}

	standardIDs := r.standardIDSubquery(filters)
	if standardIDs == nil {
		return nil, nil
	}

	var rows []StandardWeeklyFcrMetric
	db := r.DB().WithContext(ctx).
		Table("production_standard_details AS psd").
		Select("psd.week AS week, COALESCE(AVG(psd.standard_fcr), 0) AS std_fcr").
		Where("psd.week IN ?", weeks).
		Where("psd.production_standard_id IN (?)", standardIDs)

	if err := db.Group("psd.week").Order("psd.week ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) SumEggProductionWeightGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) {
	var total float64

	db := r.DB().WithContext(ctx).
		Table("recording_eggs AS re").
		Select("COALESCE(SUM(re.weight * 1000), 0)").
		Joins("JOIN recordings AS r ON r.id = re.recording_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL")

	db = applyDashboardFilters(db, filters)

	if err := db.Scan(&total).Error; err != nil {
		return 0, err
	}

	return total, nil
}

func (r *DashboardRepositoryImpl) SumEggProductionWeightKg(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) {
	grams, err := r.SumEggProductionWeightGrams(ctx, start, end, filters)
	if err != nil {
		return 0, err
	}
	return grams / 1000, nil
}

func (r *DashboardRepositoryImpl) ListProjectFlockKandangIDsByEggProduction(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]uint, error) {
	var ids []uint

	db := r.DB().WithContext(ctx).
		Table("recording_eggs AS re").
		Select("DISTINCT r.project_flock_kandangs_id").
		Joins("JOIN recordings AS r ON r.id = re.recording_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL")

	db = applyDashboardFilters(db, filters)

	if err := db.Scan(&ids).Error; err != nil {
		return nil, err
	}

	return ids, nil
}

func (r *DashboardRepositoryImpl) GetFeedUsageByUom(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]FeedUsageByUom, error) {
	var rows []FeedUsageByUom

	db := r.DB().WithContext(ctx).
		Table("recording_stocks AS rs").
		Select("COALESCE(SUM(rs.usage_qty), 0) + COALESCE(SUM(rs.pending_qty), 0) AS total_qty, LOWER(uoms.name) AS uom_name").
		Joins("JOIN recordings AS r ON r.id = rs.recording_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id").
		Joins("JOIN products AS p ON p.id = pw.product_id").
		Joins("JOIN uoms ON uoms.id = p.uom_id").
		Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ? AND UPPER(f.name) = ?", entity.FlagableTypeProduct, "PAKAN").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL")

	db = applyDashboardFilters(db, filters)

	if err := db.Group("LOWER(uoms.name)").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) SumDepletions(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) {
	var total float64

	db := r.DB().WithContext(ctx).
		Table("recording_depletions AS rd").
		Select("COALESCE(SUM(rd.qty), 0)").
		Joins("JOIN recordings AS r ON r.id = rd.recording_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL")

	db = applyDashboardFilters(db, filters)

	if err := db.Scan(&total).Error; err != nil {
		return 0, err
	}

	return total, nil
}

func (r *DashboardRepositoryImpl) SumInitialPopulation(ctx context.Context, endDate time.Time, filters *validation.DashboardFilter) (float64, error) {
	var total float64
	endOfDate := endDate.AddDate(0, 0, 1)

	db := r.DB().WithContext(ctx).
		Table("project_chickins AS pc").
		Select("COALESCE(SUM(pc.usage_qty), 0)").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pc.project_flock_kandang_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Where("pc.chick_in_date < ?", endOfDate).
		Where("pc.deleted_at IS NULL")

	db = applyDashboardFilters(db, filters)

	if err := db.Scan(&total).Error; err != nil {
		return 0, err
	}

	return total, nil
}

func (r *DashboardRepositoryImpl) SumSellingPrice(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (SellingPriceAggregate, error) {
	var result SellingPriceAggregate

	db := r.DB().WithContext(ctx).
		Table("marketing_delivery_products AS mdp").
		Select("COALESCE(SUM(mdp.total_price), 0) AS total_price, COALESCE(SUM(mdp.total_weight), 0) AS total_weight").
		Joins("JOIN marketing_products AS mp ON mp.id = mdp.marketing_product_id").
		Joins("JOIN product_warehouses AS pw ON pw.id = mp.product_warehouse_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = pw.project_flock_kandang_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Where("mdp.delivery_date IS NOT NULL").
		Where("mdp.delivery_date >= ? AND mdp.delivery_date < ?", start, end)

	db = applyDashboardFilters(db, filters)

	if err := db.Scan(&result).Error; err != nil {
		return SellingPriceAggregate{}, err
	}

	return result, nil
}

func (r *DashboardRepositoryImpl) SumSapronakCost(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) {
	var total float64

	db := r.DB().WithContext(ctx).
		Table("purchase_items AS pi").
		Select("COALESCE(SUM(pi.total_price), 0) AS total").
		Joins("JOIN products AS p ON p.id = pi.product_id").
		Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
		Joins("LEFT JOIN product_warehouses AS pw ON pw.id = pi.product_warehouse_id").
		Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.id = COALESCE(pi.project_flock_kandang_id, pw.project_flock_kandang_id)").
		Joins("LEFT JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Where("f.name IN ?", []utils.FlagType{utils.FlagDOC, utils.FlagPakan, utils.FlagOVK}).
		Where("pi.received_date IS NOT NULL").
		Where("pi.received_date >= ? AND pi.received_date < ?", start, end)

	db = applyDashboardFilters(db, filters)

	if err := db.Scan(&total).Error; err != nil {
		return 0, err
	}

	return total, nil
}

func (r *DashboardRepositoryImpl) SumBopCost(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) {
	return r.sumExpenseRealization(ctx, start, end, filters, func(db *gorm.DB) *gorm.DB {
		return db.
			Where("e.category = ?", utils.ExpenseCategoryBOP).
			Joins("LEFT JOIN nonstocks AS n ON n.id = en.nonstock_id").
			Joins("LEFT JOIN flags AS f ON f.flagable_id = n.id AND f.flagable_type = ? AND f.name = ?", entity.FlagableTypeNonstock, utils.FlagEkspedisi).
			Where("f.id IS NULL")
	})
}

func (r *DashboardRepositoryImpl) SumEkspedisiCost(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error) {
	return r.sumExpenseRealization(ctx, start, end, filters, func(db *gorm.DB) *gorm.DB {
		return db.
			Joins("JOIN nonstocks AS n ON n.id = en.nonstock_id").
			Joins("JOIN flags AS f ON f.flagable_id = n.id AND f.flagable_type = ?", entity.FlagableTypeNonstock).
			Where("f.name = ?", utils.FlagEkspedisi)
	})
}

func (r *DashboardRepositoryImpl) sumExpenseRealization(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, modifier func(*gorm.DB) *gorm.DB) (float64, error) {
	var total float64

	db := r.DB().WithContext(ctx).
		Table("expense_realizations AS er").
		Select("COALESCE(SUM(er.qty * er.price), 0) AS total").
		Joins("JOIN expense_nonstocks AS en ON en.id = er.expense_nonstock_id").
		Joins("JOIN expenses AS e ON e.id = en.expense_id").
		Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.id = en.project_flock_kandang_id").
		Joins("LEFT JOIN kandangs AS k ON k.id = COALESCE(en.kandang_id, pfk.kandang_id)").
		Where("e.realization_date >= ? AND e.realization_date < ?", start, end)

	db = applyDashboardFilters(db, filters)

	if modifier != nil {
		db = modifier(db)
	}

	if err := db.Scan(&total).Error; err != nil {
		return 0, err
	}

	return total, nil
}

func (r *DashboardRepositoryImpl) standardIDSubquery(filters *validation.DashboardFilter) *gorm.DB {
	db := r.DB().
		Table("project_flocks AS pf").
		Select("DISTINCT pf.production_standard_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Where("pf.production_standard_id > 0")

	if filters != nil {
		if len(filters.FlockIds) > 0 {
			db = db.Where("pf.id IN ?", filters.FlockIds)
		}
		if len(filters.KandangIds) > 0 {
			db = db.Where("k.id IN ?", filters.KandangIds)
		}
		if len(filters.LokasiIds) > 0 {
			db = db.Where("k.location_id IN ?", filters.LokasiIds)
		}
	}

	return db
}

func (r *DashboardRepositoryImpl) GetComparisonSeries(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, comparisonType string) ([]ComparisonSeries, error) {
	seriesExpr, labelExpr, groupExpr, orderExpr, err := comparisonSeriesColumns(comparisonType)
	if err != nil {
		return nil, err
	}

	var rows []ComparisonSeries
	db := r.DB().WithContext(ctx).
		Table("recordings AS r").
		Select(fmt.Sprintf("%s AS id, %s AS label", seriesExpr, labelExpr)).
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Joins("JOIN locations AS loc ON loc.id = k.location_id").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL")

	db = applyDashboardFilters(db, filters)

	if err := db.Group(groupExpr).Order(orderExpr).Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, comparisonType, metric string) ([]ComparisonWeeklyMetric, error) {
	seriesExpr, _, groupExpr, orderExpr, err := comparisonSeriesColumns(comparisonType)
	if err != nil {
		return nil, err
	}

	metricExpr, err := comparisonMetricColumn(metric)
	if err != nil {
		return nil, err
	}

	var rows []ComparisonWeeklyMetric
	weekExpr := `CASE
		WHEN r.day IS NULL OR r.day <= 0 THEN 1
		WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17
		ELSE ((r.day - 1) / 7 + 1)
	END`

	db := r.DB().WithContext(ctx).
		Table("recordings AS r").
		Select(fmt.Sprintf(`%s AS week,
			%s AS series_id,
			COALESCE(AVG(%s), 0) AS value`, weekExpr, seriesExpr, metricExpr)).
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Joins("JOIN locations AS loc ON loc.id = k.location_id").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL")

	db = applyDashboardFilters(db, filters)

	groupBy := fmt.Sprintf("week, %s", groupExpr)
	orderBy := fmt.Sprintf("week ASC, %s", orderExpr)
	if err := db.Group(groupBy).Order(orderBy).Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetComparisonWeeklyUniformityMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter, comparisonType string) ([]ComparisonUniformityMetric, error) {
	seriesExpr, _, groupExpr, orderExpr, err := comparisonSeriesColumns(comparisonType)
	if err != nil {
		return nil, err
	}

	var rows []ComparisonUniformityMetric
	weekExpr := dashboardUniformityWeekExpr()
	db := r.DB().WithContext(ctx).
		Table("project_flock_kandang_uniformity AS u").
		Select(fmt.Sprintf(`%s AS week,
			%s AS series_id,
			COALESCE(AVG(u.uniformity), 0) AS uniformity,
			COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`, weekExpr, seriesExpr)).
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Joins("JOIN locations AS loc ON loc.id = k.location_id").
		Joins(`JOIN (
			SELECT project_flock_kandang_id, MIN(chick_in_date)::date AS chick_in_date
			FROM project_chickins
			WHERE deleted_at IS NULL
			GROUP BY project_flock_kandang_id
		) AS pc ON pc.project_flock_kandang_id = u.project_flock_kandang_id`).
		Where("u.uniform_date IS NOT NULL").
		Where("u.uniform_date >= ? AND u.uniform_date < ?", start, end).
		Where("u.uniform_date::date >= pc.chick_in_date")

	db = applyDashboardFilters(db, filters)

	groupBy := fmt.Sprintf("%s, %s", weekExpr, groupExpr)
	orderBy := fmt.Sprintf("1 ASC, %s", orderExpr)
	if err := db.Group(groupBy).Order(orderBy).Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetEggQualityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]EggQualityWeeklyMetric, error) {
	var rows []EggQualityWeeklyMetric

	weekExpr := `CASE
		WHEN r.day IS NULL OR r.day <= 0 THEN 1
		WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17
		ELSE ((r.day - 1) / 7 + 1)
	END`

	db := r.DB().WithContext(ctx).
		Table("recording_eggs AS re").
		Select(fmt.Sprintf(`
			%s AS week,
			COALESCE(SUM(CASE WHEN f.name = ? THEN re.qty ELSE 0 END), 0) AS normal_qty,
			COALESCE(SUM(CASE WHEN f.name IN (?, ?, ?) THEN re.qty ELSE 0 END), 0) AS abnormal_qty,
			COALESCE(SUM(re.qty), 0) AS total_qty`, weekExpr),
			utils.FlagTelurUtuh,
			utils.FlagTelurPutih,
			utils.FlagTelurRetak,
			utils.FlagTelurPecah,
		).
		Joins("JOIN recordings AS r ON r.id = re.recording_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Joins("JOIN product_warehouses AS pw ON pw.id = re.product_warehouse_id").
		Joins("JOIN products AS p ON p.id = pw.product_id").
		Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
		Where("f.name IN ?", []utils.FlagType{utils.FlagTelurUtuh, utils.FlagTelurPutih, utils.FlagTelurRetak, utils.FlagTelurPecah}).
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL").
		Where("r.day IS NOT NULL AND r.day > 0")

	db = applyDashboardFilters(db, filters)

	if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetEggWeightWeeklyGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]WeeklyEggWeightMetric, error) {
	var rows []WeeklyEggWeightMetric

	weekExpr := `CASE
		WHEN r.day IS NULL OR r.day <= 0 THEN 1
		WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17
		ELSE ((r.day - 1) / 7 + 1)
	END`

	db := r.DB().WithContext(ctx).
		Table("recording_eggs AS re").
		Select(fmt.Sprintf(`
			%s AS week,
			COALESCE(SUM(re.weight * 1000), 0) AS egg_weight_grams`, weekExpr)).
		Joins("JOIN recordings AS r ON r.id = re.recording_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL").
		Where("r.day IS NOT NULL AND r.day > 0")

	db = applyDashboardFilters(db, filters)

	if err := db.Group("week").Order("week ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func (r *DashboardRepositoryImpl) GetFeedUsageWeeklyByUom(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]WeeklyFeedUsageMetric, error) {
	var rows []WeeklyFeedUsageMetric

	weekExpr := `CASE
		WHEN r.day IS NULL OR r.day <= 0 THEN 1
		WHEN UPPER(pf.category) = 'LAYING' THEN ((r.day - 1) / 7 + 1) + 17
		ELSE ((r.day - 1) / 7 + 1)
	END`

	db := r.DB().WithContext(ctx).
		Table("recording_stocks AS rs").
		Select(fmt.Sprintf(`
			%s AS week,
			COALESCE(SUM(rs.usage_qty), 0) + COALESCE(SUM(rs.pending_qty), 0) AS total_qty,
			LOWER(uoms.name) AS uom_name`, weekExpr)).
		Joins("JOIN recordings AS r ON r.id = rs.recording_id").
		Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
		Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
		Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
		Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id").
		Joins("JOIN products AS p ON p.id = pw.product_id").
		Joins("JOIN uoms ON uoms.id = p.uom_id").
		Joins("JOIN flags AS f ON f.flagable_id = p.id AND f.flagable_type = ? AND UPPER(f.name) = ?", entity.FlagableTypeProduct, "PAKAN").
		Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
		Where("r.deleted_at IS NULL").
		Where("r.day IS NOT NULL AND r.day > 0")

	db = applyDashboardFilters(db, filters)

	if err := db.Group("week, LOWER(uoms.name)").Order("week ASC").Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func comparisonSeriesColumns(comparisonType string) (string, string, string, string, error) {
	switch strings.ToUpper(strings.TrimSpace(comparisonType)) {
	case validation.ComparisonTypeFarm:
		return "loc.id", "loc.name", "loc.id, loc.name", "loc.name", nil
	case validation.ComparisonTypeFlock:
		return "pf.id", "pf.flock_name", "pf.id, pf.flock_name", "pf.flock_name", nil
	case validation.ComparisonTypeKandang:
		return "k.id", "k.name", "k.id, k.name", "k.name", nil
	default:
		return "", "", "", "", fmt.Errorf("invalid comparison_type")
	}
}

func comparisonMetricColumn(metric string) (string, error) {
	switch strings.ToLower(strings.TrimSpace(metric)) {
	case validation.MetricFcr:
		return "r.fcr_value", nil
	case validation.MetricMortality:
		return "r.cum_depletion_rate", nil
	case validation.MetricLaying:
		return "r.hen_day", nil
	case validation.MetricEggWeight:
		return "r.egg_weight", nil
	case validation.MetricFeedIntake:
		return "r.feed_intake", nil
	default:
		return "", fmt.Errorf("invalid metric")
	}
}
