package repository

import (
	"context"
	"errors"

	"github.com/gofiber/fiber/v2"
	"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"
	"gorm.io/gorm"
)

type KandangRepository interface {
	repository.BaseRepository[entity.Kandang]
	LocationExists(ctx context.Context, areaId uint) (bool, error)
	PicExists(ctx context.Context, areaId uint) (bool, error)
	KandangGroupExists(ctx context.Context, kandangGroupId uint) (bool, error)
	NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
	ProjectFlockExists(ctx context.Context, projectFlockID uint) (bool, error)
	GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error)
	HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error)
	UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error
	UpsertProjectFlockKandang(ctx context.Context, projectFlockID, kandangID uint) error
	UpdateStatusByIDs(ctx context.Context, kandangIDs []uint, status utils.KandangStatus) error
	IdExists(ctx context.Context, id uint) (bool, error)
}

type KandangRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.Kandang]
	db *gorm.DB
}

func NewKandangRepository(db *gorm.DB) KandangRepository {
	return &KandangRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.Kandang](db),
		db:                 db,
	}
}

func (r *KandangRepositoryImpl) LocationExists(ctx context.Context, locationId uint) (bool, error) {
	return repository.Exists[entity.Location](ctx, r.db, locationId)
}

func (r *KandangRepositoryImpl) PicExists(ctx context.Context, picId uint) (bool, error) {
	return repository.Exists[entity.User](ctx, r.db, picId)
}

func (r *KandangRepositoryImpl) KandangGroupExists(ctx context.Context, kandangGroupId uint) (bool, error) {
	return repository.Exists[entity.KandangGroup](ctx, r.db, kandangGroupId)
}

func (r *KandangRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
	return repository.ExistsByName[entity.Kandang](ctx, r.db, name, excludeID)
}

func (r *KandangRepositoryImpl) ProjectFlockExists(ctx context.Context, projectFlockID uint) (bool, error) {
	var count int64
	if err := r.db.WithContext(ctx).
		Model(&entity.ProjectFlock{}).
		Where("id = ?", projectFlockID).
		Where("deleted_at IS NULL").
		Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *KandangRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
	return repository.Exists[entity.Kandang](ctx, r.db, id)
}

func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error) {
	var count int64
	q := r.db.WithContext(ctx).
		Table("kandangs k").
		Joins("JOIN project_flock_kandangs pfk ON pfk.kandang_id = k.id").
		Where("pfk.project_flock_id = ?", projectFlockID).
		Where("k.status = ?", utils.KandangStatusActive).
		Where("k.deleted_at IS NULL")
	if excludeID != nil {
		q = q.Where("k.id <> ?", *excludeID)
	}
	if err := q.Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *KandangRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error) {
	kandang := new(entity.Kandang)
	err := r.db.WithContext(ctx).
		Table("kandangs k").
		Joins("JOIN project_flock_kandangs pfk ON pfk.kandang_id = k.id").
		Where("pfk.project_flock_id = ?", projectFlockID).
		Where("k.deleted_at IS NULL").
		Order("k.id ASC").
		Limit(1).
		Find(kandang).Error
	if err != nil {
		return nil, err
	}
	if kandang.Id == 0 {
		return nil, gorm.ErrRecordNotFound
	}

	return kandang, nil
}

func (r *KandangRepositoryImpl) UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error {
	sub := r.db.WithContext(ctx).
		Table("project_flock_kandangs").
		Select("kandang_id").
		Where("project_flock_id = ?", projectFlockID)

	return r.db.WithContext(ctx).
		Model(&entity.Kandang{}).
		Where("id IN (?)", sub).
		Where("deleted_at IS NULL").
		Update("status", string(status)).Error
}

func (r *KandangRepositoryImpl) UpsertProjectFlockKandang(ctx context.Context, projectFlockID, kandangID uint) error {
	var link entity.ProjectFlockKandang
	err := r.db.WithContext(ctx).
		Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
		First(&link).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		var project entity.ProjectFlock
		if err := r.db.WithContext(ctx).
			Select("id, location_id").
			First(&project, projectFlockID).Error; err != nil {
			return err
		}

		var kandang entity.Kandang
		if err := r.db.WithContext(ctx).
			Select("id, location_id").
			First(&kandang, kandangID).Error; err != nil {
			return err
		}
		if kandang.LocationId != project.LocationId {
			return fiber.NewError(fiber.StatusBadRequest, "Kandang tidak berada pada lokasi yang sama dengan project flock")
		}

		// Determine project's period from existing pivot rows so the new kandang
		// shares the same period.
		var period int
		if err := r.db.WithContext(ctx).
			Table("project_flock_kandangs").
			Where("project_flock_id = ?", projectFlockID).
			Select("COALESCE(MAX(period), 0)").
			Scan(&period).Error; err != nil {
			return err
		}
		if period <= 0 {
			period = 1
		}

		link = entity.ProjectFlockKandang{
			ProjectFlockId: projectFlockID,
			KandangId:      kandangID,
			Period:         period,
		}
		return r.db.WithContext(ctx).Create(&link).Error
	}
	return err
}

func (r *KandangRepositoryImpl) UpdateStatusByIDs(ctx context.Context, kandangIDs []uint, status utils.KandangStatus) error {
	if len(kandangIDs) == 0 {
		return nil
	}
	return r.db.WithContext(ctx).
		Model(&entity.Kandang{}).
		Where("id IN ?", kandangIDs).
		Where("deleted_at IS NULL").
		Update("status", string(status)).Error
}
