package repository

import (
	"context"
	"errors"
	"time"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
	"gorm.io/gorm"
)

type HppCostRepository interface {
	GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error)
	GetDocCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error)
	GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error)
	GetExpedisionCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error)
	GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error)
	GetOvkUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error)
	GetTotalPopulation(ctx context.Context, projectFlockKandangIDs []uint) (float64, error)
	GetPulletCost(ctx context.Context, projectFlockKandangId uint) (float64, error)
	GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error)
	GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, startDate *time.Time, endDate *time.Time) (float64, float64, error)
	GetProjectFlockIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (uint, error)
	GetTransferSourceSummary(ctx context.Context, projectFlockKandangId uint) (uint, float64, error)
	GetManualDepreciationCostByProjectFlockID(ctx context.Context, projectFlockId uint) (float64, error)
}

type HppRepositoryImpl struct {
	db *gorm.DB
}

func NewHppCostRepository(db *gorm.DB) HppCostRepository {
	return &HppRepositoryImpl{db: db}
}

func (r *HppRepositoryImpl) GetProjectFlockKandangIDs(ctx context.Context, projectFlockId uint) ([]uint, error) {
	var ids []uint
	err := r.db.WithContext(ctx).
		Table("project_flock_kandangs").
		Select("id").
		Where("project_flock_id = ?", projectFlockId).
		Scan(&ids).Error
	if err != nil {
		return nil, err
	}

	return ids, nil
}

func (r *HppRepositoryImpl) GetDocCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
	stockablePurchase := fifo.StockableKeyPurchaseItems.String()
	stockableAdjustment := fifo.StockableKeyAdjustmentIn.String()
	usableProjectChickin := fifo.UsableKeyProjectChickin.String()

	var total float64
	err := r.db.WithContext(ctx).
		Table("project_chickins AS pc").
		Select(`
			COALESCE(SUM(sa.qty * CASE
				WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
				WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
				ELSE 0
			END), 0)`,
			stockablePurchase,
			stockableAdjustment,
		).
		Joins(
			"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND (sa.stockable_type = ? OR sa.stockable_type = ?) AND sa.status = ? AND sa.allocation_purpose = ?",
			usableProjectChickin,
			stockablePurchase,
			stockableAdjustment,
			entity.StockAllocationStatusActive,
			entity.StockAllocationPurposeTraceChickin,
		).
		Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", stockablePurchase).
		Joins("LEFT JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", stockableAdjustment).
		Where("pc.project_flock_kandang_id IN (?)", projectFlockKandangIDs).
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	return total, nil
}

func (r *HppRepositoryImpl) GetBudgetCostByProjectFlockId(ctx context.Context, projectFlockId uint) (float64, error) {
	var total float64
	err := r.db.WithContext(ctx).
		Table("project_budgets AS pb").
		Select("COALESCE(SUM(pb.qty * pb.price), 0)").
		Where("pb.project_flock_id = ?", projectFlockId).
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	return total, nil
}

func (r *HppRepositoryImpl) GetExpedisionCost(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
	var total float64
	err := r.db.WithContext(ctx).
		Table("expense_nonstocks AS en").
		Select("COALESCE(SUM(er.qty * er.price), 0)").
		Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id").
		Joins("JOIN flags AS f ON f.flagable_id = en.nonstock_id AND f.flagable_type = ?", entity.FlagableTypeNonstock).
		Where("en.project_flock_kandang_id IN (?)", projectFlockKandangIDs).
		// Where("f.name = ?", utils.FlagEkspedisi).
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	return total, nil
}

func (r *HppRepositoryImpl) GetFeedUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) {
	if date == nil {
		now := time.Now()
		date = &now
	}

	stockablePurchase := fifo.StockableKeyPurchaseItems.String()
	stockableAdjustment := fifo.StockableKeyAdjustmentIn.String()
	usableRecordingStock := fifo.UsableKeyRecordingStock.String()

	var total float64
	err := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select(`
			COALESCE(SUM(sa.qty * CASE
				WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
				WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
				ELSE 0
			END), 0)`,
			stockablePurchase,
			stockableAdjustment,
		).
		Joins("JOIN recording_stocks AS rs ON rs.recording_id = r.id").
		Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id").
		Joins("JOIN flags AS f ON f.flagable_id = pw.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
		Joins(
			"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND (sa.stockable_type = ? OR sa.stockable_type = ?) AND sa.status = ? AND sa.allocation_purpose = ?",
			usableRecordingStock,
			stockablePurchase,
			stockableAdjustment,
			entity.StockAllocationStatusActive,
			entity.StockAllocationPurposeConsume,
		).
		Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", stockablePurchase).
		Joins("LEFT JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", stockableAdjustment).
		Where("rs.project_flock_kandang_id IN (?)", projectFlockKandangIDs).
		Where("r.record_datetime <= ?", *date).
		Where("f.name = ?", utils.FlagPakan).
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	return total, nil
}

func (r *HppRepositoryImpl) GetOvkUsageCost(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, error) {
	if date == nil {
		now := time.Now()
		date = &now
	}

	flags := []utils.FlagType{
		utils.FlagOVK,
		utils.FlagObat,
		utils.FlagVitamin,
		utils.FlagKimia,
	}
	stockablePurchase := fifo.StockableKeyPurchaseItems.String()
	stockableAdjustment := fifo.StockableKeyAdjustmentIn.String()
	usableRecordingStock := fifo.UsableKeyRecordingStock.String()

	var total float64
	err := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select(`
			COALESCE(SUM(sa.qty * CASE
				WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
				WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
				ELSE 0
			END), 0)`,
			stockablePurchase,
			stockableAdjustment,
		).
		Joins("JOIN recording_stocks AS rs ON rs.recording_id = r.id").
		Joins("JOIN product_warehouses AS pw ON pw.id = rs.product_warehouse_id").
		Joins(
			"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = rs.id AND (sa.stockable_type = ? OR sa.stockable_type = ?) AND sa.status = ? AND sa.allocation_purpose = ?",
			usableRecordingStock,
			stockablePurchase,
			stockableAdjustment,
			entity.StockAllocationStatusActive,
			entity.StockAllocationPurposeConsume,
		).
		Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", stockablePurchase).
		Joins("LEFT JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", stockableAdjustment).
		Where("rs.project_flock_kandang_id IN (?)", projectFlockKandangIDs).
		Where("r.record_datetime <= ?", *date).
		Where("EXISTS (SELECT 1 FROM flags f WHERE f.flagable_id = pw.product_id AND f.flagable_type = ? AND f.name IN ?)", entity.FlagableTypeProduct, flags).
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	return total, nil
}

func (r *HppRepositoryImpl) GetTotalPopulation(ctx context.Context, projectFlockKandangIDs []uint) (float64, error) {
	var total float64
	err := r.db.WithContext(ctx).
		Table("project_chickins AS pc").
		Select("COALESCE(SUM(pc.usage_qty), 0)").
		Where("pc.project_flock_kandang_id IN (?)", projectFlockKandangIDs).
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	return total, nil
}

func (r *HppRepositoryImpl) GetPulletCost(ctx context.Context, projectFlockKandangId uint) (float64, error) {
	stockablePurchase := fifo.StockableKeyPurchaseItems.String()
	stockableTransferIn := fifo.StockableKeyStockTransferIn.String()
	stockableAdjustment := fifo.StockableKeyAdjustmentIn.String()
	usableProjectChickin := fifo.UsableKeyProjectChickin.String()

	var total float64
	err := r.db.WithContext(ctx).
		Table("project_chickins AS pc").
		Select(`
				COALESCE(SUM(sa.qty * CASE
					WHEN sa.stockable_type = ? THEN COALESCE(pi.price, 0)
					WHEN sa.stockable_type = ? THEN COALESCE(tpi.price, 0)
					WHEN sa.stockable_type = ? THEN COALESCE(ast.price, 0)
					ELSE 0
				END), 0)`,
			stockablePurchase,
			stockableTransferIn,
			stockableAdjustment,
		).
		Joins("JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = pc.id AND sa.status = ? AND sa.allocation_purpose = ?", usableProjectChickin, entity.StockAllocationStatusActive, entity.StockAllocationPurposeTraceChickin).
		Joins("LEFT JOIN purchase_items AS pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", stockablePurchase).
		Joins("LEFT JOIN stock_allocations AS tsa ON tsa.usable_type = ? AND tsa.usable_id = sa.stockable_id AND sa.stockable_type = ? AND tsa.stockable_type = ? AND tsa.status = ? AND tsa.allocation_purpose = ?", stockableTransferIn, stockableTransferIn, stockablePurchase, entity.StockAllocationStatusActive, entity.StockAllocationPurposeConsume).
		Joins("LEFT JOIN purchase_items AS tpi ON tpi.id = tsa.stockable_id").
		Joins("LEFT JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND sa.stockable_type = ?", stockableAdjustment).
		Where("pc.project_flock_kandang_id = ?", projectFlockKandangId).
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	return total, nil
}

func (r *HppRepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) {
	if date == nil {
		now := time.Now()
		date = &now
	}

	var totals struct {
		TotalPieces   float64
		TotalWeightKg float64
	}
	err := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select("COALESCE(SUM(re.qty), 0) AS total_pieces, COALESCE(SUM(re.weight), 0)AS total_weight_kg").
		Joins("JOIN recording_eggs AS re ON re.recording_id = r.id").
		Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs).
		Where("r.record_datetime <= ?", *date).
		Scan(&totals).Error
	if err != nil {
		return 0, 0, err
	}

	var adjustmentTotalWeight float64
	adjustmentSubQuery := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select("DISTINCT ast.id AS adjustment_id, ast.price AS price").
		Joins("JOIN recording_eggs AS re ON re.recording_id = r.id").
		Joins("JOIN stock_transfer_details AS std ON std.dest_product_warehouse_id = re.product_warehouse_id").
		Joins(
			"JOIN stock_allocations AS sa ON sa.usable_type = ? AND sa.usable_id = std.id AND sa.stockable_type = ? AND sa.status = ? AND sa.allocation_purpose = ?",
			fifo.UsableKeyStockTransferOut.String(),
			fifo.StockableKeyAdjustmentIn.String(),
			entity.StockAllocationStatusActive,
			entity.StockAllocationPurposeConsume,
		).
		Joins("JOIN adjustment_stocks AS ast ON ast.id = sa.stockable_id AND ast.product_warehouse_id = std.source_product_warehouse_id").
		Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs).
		Where("r.record_datetime <= ?", *date)

	err = r.db.WithContext(ctx).
		Table("(?) AS adjustment_sources", adjustmentSubQuery).
		Select("COALESCE(SUM(adjustment_sources.price), 0)").
		Scan(&adjustmentTotalWeight).Error
	if err != nil {
		return 0, 0, err
	}

	totals.TotalWeightKg += adjustmentTotalWeight

	return totals.TotalPieces, totals.TotalWeightKg, nil
}

func (r *HppRepositoryImpl) GetEggTerjualPiecesAndWeightKgByProjectFlockKandangIds(
	ctx context.Context,
	projectFlockKandangIDs []uint,
	startDate *time.Time,
	endDate *time.Time,
) (float64, float64, error) {

	if endDate == nil {
		now := time.Now()
		endDate = &now
	}

	type subResult struct {
		UsableID    uint
		MdpUsageQty float64
		MdpWeight   float64
	}

	subQuery := r.db.WithContext(ctx).
		Table("recordings AS r").
		Select(`
			DISTINCT sa.usable_id,
			mdp.usage_qty  AS mdp_usage_qty,
			mdp.total_weight AS mdp_weight
		`).
		Joins("JOIN recording_eggs re ON re.recording_id = r.id").
		Joins(
			"JOIN stock_allocations sa ON sa.stockable_type = ? AND sa.stockable_id = re.id AND sa.usable_type = ? AND sa.status = ? AND sa.allocation_purpose = ?",
			fifo.StockableKeyRecordingEgg.String(),
			fifo.UsableKeyMarketingDelivery.String(),
			entity.StockAllocationStatusActive,
			entity.StockAllocationPurposeConsume,
		).
		Joins("JOIN marketing_delivery_products mdp ON mdp.id = sa.usable_id").
		Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs).
		Where("r.record_datetime <= ?", *endDate).
		Where("mdp.delivery_date <= ?", *startDate)

	var totals struct {
		TotalPieces float64
		TotalWeight float64
	}

	err := r.db.WithContext(ctx).
		Table("(?) AS x", subQuery).
		Select(`
			COALESCE(SUM(x.mdp_usage_qty), 0)  AS total_pieces,
			COALESCE(SUM(x.mdp_weight), 0)     AS total_weight
		`).
		Scan(&totals).Error

	if err != nil {
		return 0, 0, err
	}

	return totals.TotalPieces, totals.TotalWeight, nil
}

func (r *HppRepositoryImpl) GetProjectFlockIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (uint, error) {
	var projectFlockID uint
	err := r.db.WithContext(ctx).
		Table("project_flock_kandangs").
		Select("project_flock_id").
		Where("id = ?", projectFlockKandangId).
		Scan(&projectFlockID).Error
	if err != nil {
		return 0, err
	}

	return projectFlockID, nil
}

func (r *HppRepositoryImpl) GetTransferSourceSummary(ctx context.Context, projectFlockKandangId uint) (uint, float64, error) {
	var summary struct {
		ProjectFlockID uint
		TotalQty       float64
	}
	err := r.db.WithContext(ctx).
		Table("laying_transfer_targets AS ltt").
		Select("lt.from_project_flock_id AS project_flock_id, COALESCE(SUM(ltt.total_qty), 0) AS total_qty").
		Joins("JOIN laying_transfers AS lt ON lt.id = ltt.laying_transfer_id").
		Where("lt.deleted_at IS NULL").
		Where("ltt.deleted_at IS NULL").
		Where("lt.executed_at IS NOT NULL").
		Where("ltt.target_project_flock_kandang_id = ?", projectFlockKandangId).
		Group("lt.from_project_flock_id").
		Scan(&summary).Error
	if err != nil {
		return 0, 0, err
	}

	return summary.ProjectFlockID, summary.TotalQty, nil
}

func (r *HppRepositoryImpl) GetManualDepreciationCostByProjectFlockID(ctx context.Context, projectFlockId uint) (float64, error) {
	type row struct {
		TotalCost float64
	}

	var selected row
	err := r.db.WithContext(ctx).
		Table("farm_depreciation_manual_inputs").
		Select("total_cost").
		Where("project_flock_id = ?", projectFlockId).
		Limit(1).
		Take(&selected).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return 0, nil
	}
	if err != nil {
		return 0, err
	}

	return selected.TotalCost, nil
}
