package repository

import (
	"context"
	"math"

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

type ProjectFlockPopulationRepository interface {
	GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectFlockPopulation, error)
	ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error)
	GetByProjectChickinIDAndProductWarehouseID(ctx context.Context, projectChickinID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
	GetByProjectFlockKandangIDAndProductWarehouseID(ctx context.Context, projectFlockKandangID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error)
	GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
	GetTotalQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error)
	GetAvailableQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
	GetTotalChickInByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int64, error)
	ResyncUsageByProjectFlockKandangID(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error

	CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
	PatchOne(ctx context.Context, id uint, updates map[string]any, modifier func(*gorm.DB) *gorm.DB) error

	WithTx(tx *gorm.DB) ProjectFlockPopulationRepository
	DB() *gorm.DB
}

type projectFlockPopulationRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.ProjectFlockPopulation]
}

func NewProjectFlockPopulationRepository(db *gorm.DB) ProjectFlockPopulationRepository {
	return &projectFlockPopulationRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlockPopulation](db),
	}
}

func (r *projectFlockPopulationRepositoryImpl) WithTx(tx *gorm.DB) ProjectFlockPopulationRepository {
	return &projectFlockPopulationRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlockPopulation](tx),
	}
}

func (r *projectFlockPopulationRepositoryImpl) DB() *gorm.DB {
	return r.BaseRepositoryImpl.DB()
}

func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectFlockPopulation, error) {
	var records []entity.ProjectFlockPopulation
	err := r.DB().WithContext(ctx).
		Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
		Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
		Where("project_chickins.deleted_at IS NULL").
		Preload("ProjectChickin").
		Find(&records).Error
	if err != nil {
		return nil, err
	}
	return records, nil
}

func (r *projectFlockPopulationRepositoryImpl) ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error) {
	var count int64
	err := r.DB().WithContext(ctx).
		Where("project_chickin_id = ?", projectChickinID).
		Model(&entity.ProjectFlockPopulation{}).
		Count(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *projectFlockPopulationRepositoryImpl) GetByProjectChickinIDAndProductWarehouseID(ctx context.Context, projectChickinID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error) {
	var records []entity.ProjectFlockPopulation
	err := r.DB().WithContext(ctx).
		Where("project_chickin_id = ? AND product_warehouse_id = ?", projectChickinID, productWarehouseID).
		Find(&records).Error
	if err != nil {
		return nil, err
	}
	return records, nil
}

func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangIDAndProductWarehouseID(ctx context.Context, projectFlockKandangID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error) {
	var records []entity.ProjectFlockPopulation
	err := r.DB().WithContext(ctx).
		Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
		Where("project_chickins.project_flock_kandang_id = ? AND project_flock_populations.product_warehouse_id = ?", projectFlockKandangID, productWarehouseID).
		Where("project_chickins.deleted_at IS NULL").
		Find(&records).Error
	if err != nil {
		return nil, err
	}
	return records, nil
}

func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
	var total float64
	err := r.DB().WithContext(ctx).
		Table("project_flock_populations").
		Select("COALESCE(SUM(total_qty - total_used_qty), 0) AS available_qty").
		Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
		Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
		Where("project_chickins.deleted_at IS NULL").
		Where("project_flock_populations.deleted_at IS NULL").
		Scan(&total).Error
	if err != nil {
		return 0, err
	}
	return total, nil
}

func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error) {
	var total float64
	err := r.DB().WithContext(ctx).
		Table("project_flock_populations").
		Select("COALESCE(SUM(project_flock_populations.total_qty - project_flock_populations.total_used_qty), 0)").
		Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
		Where("project_flock_populations.product_warehouse_id = ?", productWarehouseID).
		Where("project_chickins.deleted_at IS NULL").
		Where("project_flock_populations.deleted_at IS NULL").
		Scan(&total).Error
	if err != nil {
		return 0, err
	}
	return total, nil
}

func (r *projectFlockPopulationRepositoryImpl) GetAvailableQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) {
	var total float64
	err := r.DB().WithContext(ctx).
		Table("project_flock_populations").
		Select("COALESCE(SUM(total_qty - total_used_qty), 0) AS total_qty").
		Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
		Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
		Where("project_chickins.deleted_at IS NULL").
		Where("project_flock_populations.deleted_at IS NULL").
		Scan(&total).Error
	if err != nil {
		return 0, err
	}
	if total < 0 {
		total = 0
	}
	return total, nil
}

func (r *projectFlockPopulationRepositoryImpl) GetTotalChickInByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int64, error) {
	var total float64
	err := r.DB().WithContext(ctx).
		Table("project_flock_populations").
		Select("COALESCE(SUM(project_flock_populations.total_qty - project_flock_populations.total_used_qty), 0) AS total_qty").
		Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
		Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
		Where("project_chickins.deleted_at IS NULL").
		Where("project_flock_populations.deleted_at IS NULL").
		Scan(&total).Error
	if err != nil {
		return 0, err
	}

	if total < 0 {
		total = 0
	}

	return int64(math.Round(total)), nil
}

func (r *projectFlockPopulationRepositoryImpl) ResyncUsageByProjectFlockKandangID(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error {
	if projectFlockKandangID == 0 {
		return nil
	}

	idsSubquery := `
		SELECT pfp.id
		FROM project_flock_populations pfp
		JOIN project_chickins pc ON pc.id = pfp.project_chickin_id
		WHERE pc.project_flock_kandang_id = ?
	`

	updateWithAlloc := `
		UPDATE project_flock_populations p
		SET total_used_qty = COALESCE(a.used, 0)
		FROM (
			SELECT stockable_id, SUM(qty) AS used
			FROM stock_allocations
			WHERE stockable_type = 'PROJECT_FLOCK_POPULATION'
			  AND status = 'ACTIVE'
			  AND allocation_purpose = 'CONSUME'
			GROUP BY stockable_id
		) a
		WHERE p.id = a.stockable_id
		  AND p.id IN (` + idsSubquery + `)
	`

	resetMissing := `
		UPDATE project_flock_populations p
		SET total_used_qty = 0
		WHERE p.id IN (` + idsSubquery + `)
			AND NOT EXISTS (
				SELECT 1
				FROM stock_allocations sa
				WHERE sa.stockable_type = 'PROJECT_FLOCK_POPULATION'
				  AND sa.status = 'ACTIVE'
				  AND sa.allocation_purpose = 'CONSUME'
				  AND sa.stockable_id = p.id
			)
	`

	db := r.DB().WithContext(ctx)
	if tx != nil {
		db = tx.WithContext(ctx)
	}

	if err := db.Exec(updateWithAlloc, projectFlockKandangID).Error; err != nil {
		return err
	}
	if err := db.Exec(resetMissing, projectFlockKandangID).Error; err != nil {
		return err
	}
	return nil
}
