package repository

import (
	"context"
	"fmt"
	"strings"

	"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	"gorm.io/gorm"
)

type ProjectflockRepository interface {
	repository.BaseRepository[entity.ProjectFlock]
	GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error)
	GetAllWithFiltersScoped(ctx context.Context, offset, limit int, params *validation.Query, locationIDs []uint, restrict bool) ([]entity.ProjectFlock, int64, error)
	WithDefaultRelations() func(*gorm.DB) *gorm.DB
	ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error)
	GetNextPeriodsForKandangs(ctx context.Context, kandangIDs []uint) (map[uint]int, error)
	GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error)
	GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error)
	GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error)
	IdExists(ctx context.Context, id uint) (bool, error)
	AreaExists(ctx context.Context, id uint) (bool, error)
	ProductionStandardExists(ctx context.Context, id uint) (bool, error)
	LocationExists(ctx context.Context, id uint) (bool, error)
}
type KandangPeriodRow struct {
	Id           uint
	Name         string
	LatestPeriod int
}

type ProjectflockRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.ProjectFlock]
}

func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository {
	return &ProjectflockRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db),
	}
}

func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
	return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
		return r.applyQueryFilters(r.WithDefaultRelations()(db), params)
	})
}

func (r *ProjectflockRepositoryImpl) GetAllWithFiltersScoped(ctx context.Context, offset, limit int, params *validation.Query, locationIDs []uint, restrict bool) ([]entity.ProjectFlock, int64, error) {
	return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
		db = r.applyQueryFilters(r.WithDefaultRelations()(db), params)
		if restrict {
			if len(locationIDs) == 0 {
				return db.Where("1 = 0")
			}
			db = db.Where("project_flocks.location_id IN ?", locationIDs)
		}
		return db
	})
}

func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.
			Preload("CreatedUser").
			Preload("Area").
			Preload("ProductionStandard").
			Preload("ProductionStandard.ProductionStandardDetails").
			Preload("Location").
			Preload("Kandangs").
			Preload("KandangHistory").
			Preload("KandangHistory.Kandang").
			Preload("Budgets").
			Preload("Budgets.Nonstock").
			Preload("Budgets.Nonstock.Uom")
	}
}

func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
	if params == nil {
		return db
	}

	if params.Category != "" {
		db = db.Where("project_flocks.category = ?", params.Category)
	}
	if params.AreaId > 0 {
		db = db.Where("project_flocks.area_id = ?", params.AreaId)
	}
	if params.LocationId > 0 {
		db = db.Where("project_flocks.location_id = ?", params.LocationId)
	}
	if params.Period > 0 {
		db = db.Where(`
        EXISTS (
            SELECT 1
            FROM project_flock_kandangs pfk
            WHERE pfk.project_flock_id = project_flocks.id
              AND pfk.period = ?
        )`, params.Period)
	}
	if len(params.KandangIds) > 0 {
		db = db.Where(`
        EXISTS (
            SELECT 1
            FROM project_flock_kandangs pfk
            WHERE pfk.project_flock_id = project_flocks.id
              AND pfk.kandang_id IN ?
        )`, params.KandangIds)
	}
	if params.Status != "" {
		db = db.Where(`
		EXISTS (
			SELECT 1
			FROM approvals latest_approval
			WHERE latest_approval.approvable_type = ?
			  AND latest_approval.approvable_id = project_flocks.id
			  AND latest_approval.id = (
				  SELECT a2.id
				  FROM approvals a2
				  WHERE a2.approvable_type = ?
				    AND a2.approvable_id = project_flocks.id
				  ORDER BY a2.id DESC
				  LIMIT 1
			  )
			  AND LOWER(latest_approval.step_name) = LOWER(?)
		)`,
			utils.ApprovalWorkflowProjectFlock.String(),
			utils.ApprovalWorkflowProjectFlock.String(),
			params.Status,
		)
	}

	db = r.applySearchFilters(db, params.Search)

	for _, expr := range r.buildOrderExpressions(params.SortBy, params.SortOrder) {
		db = db.Order(expr)
	}

	return db
}

func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch string) *gorm.DB {
	if rawSearch == "" {
		return db
	}

	normalized := strings.ToLower(strings.TrimSpace(rawSearch))
	if normalized == "" {
		return db
	}

	likeQuery := "%" + normalized + "%"
	return db.
		Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id").
		Joins("LEFT JOIN production_standards ON production_standards.id = project_flocks.production_standard_id").
		Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id").
		Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
		Where(`
			LOWER(areas.name) LIKE ?
            OR LOWER(project_flocks.category) LIKE ?
			OR LOWER(production_standards.name) LIKE ?
			OR LOWER(locations.name) LIKE ?
			OR LOWER(locations.address) LIKE ?
			OR LOWER(created_users.name) LIKE ?
			OR LOWER(created_users.email) LIKE ?
			OR LOWER(project_flocks.flock_name) LIKE ?
			OR LOWER(TRIM(regexp_replace(project_flocks.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g'))) LIKE ?
			OR EXISTS (
				SELECT 1 FROM project_flock_kandangs
				WHERE project_flock_kandangs.project_flock_id = project_flocks.id
				  AND LOWER(CAST(project_flock_kandangs.period AS TEXT)) LIKE ?
			)
			OR EXISTS (
				SELECT 1 FROM kandangs
				JOIN project_flock_kandangs pfk ON pfk.kandang_id = kandangs.id
				WHERE pfk.project_flock_id = project_flocks.id
				AND LOWER(kandangs.name) LIKE ?
			)
		`,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
			likeQuery,
		)
}

func (r *ProjectflockRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
	return repository.Exists[entity.ProjectFlock](ctx, r.DB(), id)
}

func (r *ProjectflockRepositoryImpl) AreaExists(ctx context.Context, id uint) (bool, error) {
	return repository.Exists[entity.Area](ctx, r.DB(), id)
}

func (r *ProjectflockRepositoryImpl) ProductionStandardExists(ctx context.Context, id uint) (bool, error) {
	return repository.Exists[entity.ProductionStandard](ctx, r.DB(), id)
}

func (r *ProjectflockRepositoryImpl) LocationExists(ctx context.Context, id uint) (bool, error) {
	return repository.Exists[entity.Location](ctx, r.DB(), id)
}

func (r *ProjectflockRepositoryImpl) GetNextPeriodsForKandangs(ctx context.Context, kandangIDs []uint) (map[uint]int, error) {
	result := make(map[uint]int)
	if len(kandangIDs) == 0 {
		return result, nil
	}

	unique := uniqueUintSlice(kandangIDs)
	for _, id := range unique {
		result[id] = 1
	}

	type periodRow struct {
		KandangID uint
		MaxPeriod int
	}

	var rows []periodRow
	if err := r.DB().WithContext(ctx).
		Table("project_flock_kandangs").
		Select("kandang_id, COALESCE(MAX(period), 0) AS max_period").
		Where("kandang_id IN ?", unique).
		Group("kandang_id").
		Scan(&rows).Error; err != nil {
		return nil, err
	}

	for _, row := range rows {
		if row.MaxPeriod > 0 {
			result[row.KandangID] = row.MaxPeriod + 1
		}
	}

	return result, nil
}

func (r *ProjectflockRepositoryImpl) GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error) {
	if projectFlockID == 0 {
		return 0, nil
	}

	var currentPeriod int
	if err := r.DB().WithContext(ctx).
		Table("project_flock_kandangs").
		Where("project_flock_id = ?", projectFlockID).
		Select("COALESCE(MAX(period), 0)").
		Scan(&currentPeriod).Error; err != nil {
		return 0, err
	}

	return currentPeriod, nil
}

func (r *ProjectflockRepositoryImpl) GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error) {
	rows := make([]KandangPeriodRow, 0)
	if err := r.DB().WithContext(ctx).
		Table("kandangs AS k").
		Select("k.id, k.name, COALESCE(MAX(pfk.period), 0) AS latest_period").
		Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.kandang_id = k.id").
		Where("k.location_id = ?", locationID).
		Where("k.deleted_at IS NULL").
		Group("k.id, k.name").
		Order("k.id ASC").
		Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func uniqueUintSlice(values []uint) []uint {
	seen := make(map[uint]struct{}, len(values))
	result := make([]uint, 0, len(values))
	for _, v := range values {
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		result = append(result, v)
	}
	return result
}

func (r *ProjectflockRepositoryImpl) buildOrderExpressions(sortBy, sortOrder string) []string {
	direction := "ASC"
	if strings.ToLower(sortOrder) == "desc" {
		direction = "DESC"
	}

	switch sortBy {
	case "area":
		return []string{
			fmt.Sprintf("(SELECT name FROM areas WHERE areas.id = project_flocks.area_id) %s", direction),
			fmt.Sprintf("project_flocks.id %s", direction),
		}
	case "location":
		return []string{
			fmt.Sprintf("(SELECT name FROM locations WHERE locations.id = project_flocks.location_id) %s", direction),
			fmt.Sprintf("project_flocks.id %s", direction),
		}
	case "kandangs":
		return []string{
			fmt.Sprintf("(SELECT COUNT(*) FROM project_flock_kandangs pfk WHERE pfk.project_flock_id = project_flocks.id) %s", direction),
			fmt.Sprintf("project_flocks.id %s", direction),
		}
	case "period":
		return []string{
			fmt.Sprintf("(SELECT COALESCE(MAX(period), 0) FROM project_flock_kandangs pfk WHERE pfk.project_flock_id = project_flocks.id) %s", direction),
			fmt.Sprintf("project_flocks.id %s", direction),
		}
	default:
		return []string{
			"project_flocks.flock_name ASC",
			"project_flocks.id ASC",
		}
	}
}

func (r *ProjectflockRepositoryImpl) ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error) {
	var count int64
	q := r.DB().WithContext(ctx).Model(&entity.ProjectFlock{}).Where("flock_name = ?", flockName)
	if excludeID != nil && *excludeID != 0 {
		q = q.Where("id <> ?", *excludeID)
	}
	if err := q.Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *ProjectflockRepositoryImpl) GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error) {
	var projectFlocks []entity.ProjectFlock
	err := r.DB().WithContext(ctx).
		Joins("JOIN project_flock_kandangs ON project_flock_kandangs.project_flock_id = project_flocks.id").
		Where("project_flocks.location_id = ?", locationID).
		Where("project_flock_kandangs.closed_at IS NULL").
		Group("project_flocks.id").
		Find(&projectFlocks).Error
	if err != nil {
		return nil, err
	}
	return projectFlocks, nil
}
