package repository

import (
	"context"
	"errors"
	"time"

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

type LayingTransferTargetRepository interface {
	repository.BaseRepository[entity.LayingTransferTarget]
	GetByLayingTransferId(ctx context.Context, layingTransferId uint) ([]entity.LayingTransferTarget, error)
	GetActiveDownstreamConsumptions(ctx context.Context, targetIDs []uint) ([]TargetDownstreamConsumption, error)
	GetEarliestRecordingDateByTarget(ctx context.Context, targetProjectFlockKandangID uint, sinceDate time.Time) (*time.Time, error)
	CountActiveTransferSourceConsumeAllocations(ctx context.Context, transferID uint, productWarehouseID uint) (int64, error)
	SyncPopulationUsageByProjectFlockKandang(ctx context.Context, projectFlockKandangID uint) error
}

type LayingTransferTargetRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.LayingTransferTarget]
	db *gorm.DB
}

type TargetDownstreamConsumption struct {
	UsableType string `gorm:"column:usable_type"`
	UsableID   uint   `gorm:"column:usable_id"`
}

func NewLayingTransferTargetRepository(db *gorm.DB) LayingTransferTargetRepository {
	return &LayingTransferTargetRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.LayingTransferTarget](db),
		db:                 db,
	}
}

func (r *LayingTransferTargetRepositoryImpl) GetByLayingTransferId(ctx context.Context, layingTransferId uint) ([]entity.LayingTransferTarget, error) {
	var targets []entity.LayingTransferTarget
	err := r.db.WithContext(ctx).
		Where("laying_transfer_id = ?", layingTransferId).
		Order("created_at DESC").
		Find(&targets).Error
	if err != nil {
		return nil, err
	}
	return targets, nil
}

func (r *LayingTransferTargetRepositoryImpl) GetActiveDownstreamConsumptions(ctx context.Context, targetIDs []uint) ([]TargetDownstreamConsumption, error) {
	if len(targetIDs) == 0 {
		return nil, nil
	}

	var rows []TargetDownstreamConsumption
	err := r.db.WithContext(ctx).
		Table("stock_allocations").
		Select("usable_type, usable_id").
		Where("stockable_type = ?", fifo.StockableKeyTransferToLayingIn.String()).
		Where("stockable_id IN ?", targetIDs).
		Where("status = ?", entity.StockAllocationStatusActive).
		Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
		Where("deleted_at IS NULL").
		Group("usable_type, usable_id").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (r *LayingTransferTargetRepositoryImpl) GetEarliestRecordingDateByTarget(ctx context.Context, targetProjectFlockKandangID uint, sinceDate time.Time) (*time.Time, error) {
	if targetProjectFlockKandangID == 0 {
		return nil, nil
	}

	var earliest entity.Recording
	query := r.db.WithContext(ctx).
		Model(&entity.Recording{}).
		Where("project_flock_kandangs_id = ?", targetProjectFlockKandangID).
		Where("deleted_at IS NULL")
	if !sinceDate.IsZero() {
		query = query.Where("record_datetime >= ?", sinceDate)
	}
	if err := query.Order("record_datetime ASC").Limit(1).Take(&earliest).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}

	d := earliest.RecordDatetime.UTC()
	return &d, nil
}

func (r *LayingTransferTargetRepositoryImpl) CountActiveTransferSourceConsumeAllocations(ctx context.Context, transferID uint, productWarehouseID uint) (int64, error) {
	if transferID == 0 || productWarehouseID == 0 {
		return 0, nil
	}

	var count int64
	err := r.db.WithContext(ctx).
		Model(&entity.StockAllocation{}).
		Where("product_warehouse_id = ?", productWarehouseID).
		Where("usable_type = ?", fifo.UsableKeyTransferToLayingOut.String()).
		Where("usable_id = ?", transferID).
		Where("status = ?", entity.StockAllocationStatusActive).
		Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
		Count(&count).Error
	if err != nil {
		return 0, err
	}
	return count, nil
}

func (r *LayingTransferTargetRepositoryImpl) SyncPopulationUsageByProjectFlockKandang(ctx context.Context, projectFlockKandangID uint) error {
	if projectFlockKandangID == 0 {
		return nil
	}

	var populationIDs []uint
	if err := r.db.WithContext(ctx).
		Table("project_flock_populations pfp").
		Select("pfp.id").
		Joins("JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
		Where("pc.project_flock_kandang_id = ?", projectFlockKandangID).
		Pluck("pfp.id", &populationIDs).Error; err != nil {
		return err
	}
	if len(populationIDs) == 0 {
		return nil
	}

	type usageRow struct {
		StockableID uint    `gorm:"column:stockable_id"`
		Used        float64 `gorm:"column:used"`
	}
	var usageRows []usageRow
	if err := r.db.WithContext(ctx).
		Table("stock_allocations").
		Select("stockable_id, COALESCE(SUM(qty), 0) AS used").
		Where("stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
		Where("status = ?", entity.StockAllocationStatusActive).
		Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
		Where("stockable_id IN ?", populationIDs).
		Group("stockable_id").
		Scan(&usageRows).Error; err != nil {
		return err
	}

	if err := r.db.WithContext(ctx).
		Model(&entity.ProjectFlockPopulation{}).
		Where("id IN ?", populationIDs).
		Update("total_used_qty", 0).Error; err != nil {
		return err
	}

	for _, row := range usageRows {
		if err := r.db.WithContext(ctx).
			Model(&entity.ProjectFlockPopulation{}).
			Where("id = ?", row.StockableID).
			Update("total_used_qty", row.Used).Error; err != nil {
			return err
		}
	}

	return nil
}
