package repository

import (
	"context"
	"fmt"
	"strings"

	"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 TransferLayingRepository interface {
	repository.BaseRepository[entity.LayingTransfer]
	GetByTransferNumber(ctx context.Context, transferNumber string) (*entity.LayingTransfer, error)
	IdExists(ctx context.Context, id uint) (bool, error)
	GetLatestApprovedBySourceKandang(ctx context.Context, sourceProjectFlockKandangID uint) (*entity.LayingTransfer, error)
	GetLatestApprovedByTargetKandang(ctx context.Context, targetProjectFlockKandangID uint) (*entity.LayingTransfer, error)

	// Tambah method baru untuk query dengan filter lengkap
	GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error)

	// Get sequence for movement number
	GetNextMovementNumber(ctx context.Context) (int64, error)
	GenerateMovementNumber(ctx context.Context) (string, error)
}

type TransferLayingRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.LayingTransfer]
	db *gorm.DB
}

func NewTransferLayingRepository(db *gorm.DB) TransferLayingRepository {
	return &TransferLayingRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.LayingTransfer](db),
		db:                 db,
	}
}

func (r *TransferLayingRepositoryImpl) GetNextMovementNumber(ctx context.Context) (int64, error) {
	var seq int64
	err := r.db.WithContext(ctx).Raw("SELECT nextval('transfer_laying_seq')").Scan(&seq).Error
	if err != nil {
		return 0, err
	}
	return seq, nil
}

func (r *TransferLayingRepositoryImpl) GenerateMovementNumber(ctx context.Context) (string, error) {
	seq, err := r.GetNextMovementNumber(ctx)
	if err != nil {
		return "", err
	}

	movementNumber := fmt.Sprintf("TL-%05d", seq)
	return movementNumber, nil
}

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

func (r *TransferLayingRepositoryImpl) GetByTransferNumber(ctx context.Context, transferNumber string) (*entity.LayingTransfer, error) {
	var transfer entity.LayingTransfer
	err := r.db.WithContext(ctx).
		Where("transfer_number = ?", transferNumber).
		Where("deleted_at IS NULL").
		First(&transfer).Error
	if err != nil {
		return nil, err
	}
	return &transfer, nil
}

type GetAllFilterParams struct {
	Search           string
	StartDate        string
	EndDate          string
	FlockSource      []uint
	FlockDestination []uint
	Status           []string
	LocationIDs      []uint
	LocationRestrict bool
}

func (r *TransferLayingRepositoryImpl) GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error) {
	var records []entity.LayingTransfer
	var total int64

	q := r.db.WithContext(ctx).Model(&entity.LayingTransfer{})

	if params.Search != "" {
		searchPattern := "%" + params.Search + "%"
		q = q.Joins("LEFT JOIN project_flocks AS pf_from ON laying_transfers.from_project_flock_id = pf_from.id").
			Joins("LEFT JOIN project_flocks AS pf_to ON laying_transfers.to_project_flock_id = pf_to.id").
			Where("laying_transfers.transfer_number ILIKE ? OR laying_transfers.notes ILIKE ? OR pf_from.flock_name ILIKE ? OR pf_to.flock_name ILIKE ?",
				searchPattern, searchPattern, searchPattern, searchPattern)
	}

	if params.StartDate != "" && params.EndDate != "" {
		q = q.Where("transfer_date::date >= ?::date AND transfer_date::date <= ?::date",
			params.StartDate, params.EndDate)
	} else if params.StartDate != "" {
		q = q.Where("transfer_date::date >= ?::date", params.StartDate)
	} else if params.EndDate != "" {
		q = q.Where("transfer_date::date <= ?::date", params.EndDate)
	}

	if len(params.FlockSource) > 0 {
		q = q.Where("from_project_flock_id IN ?", params.FlockSource)
	}

	if len(params.FlockDestination) > 0 {
		q = q.Where("to_project_flock_id IN ?", params.FlockDestination)
	}

	if len(params.Status) > 0 {
		statusConditions := []string{}
		statusValues := []interface{}{}

		for _, status := range params.Status {
			switch status {
			case "PENDING":
				statusConditions = append(statusConditions,
					"NOT EXISTS (SELECT 1 FROM approvals WHERE approvable_type = 'TRANSFER_TO_LAYINGS' AND approvable_id = laying_transfers.id)")

			case "APPROVED":
				statusConditions = append(statusConditions,
					"EXISTS (SELECT 1 FROM approvals WHERE approvable_type = 'TRANSFER_TO_LAYINGS' AND approvable_id = laying_transfers.id AND action = 'APPROVED' ORDER BY created_at DESC LIMIT 1)")

			case "REJECTED":
				statusConditions = append(statusConditions,
					"EXISTS (SELECT 1 FROM approvals WHERE approvable_type = 'TRANSFER_TO_LAYINGS' AND approvable_id = laying_transfers.id AND action = 'REJECTED' ORDER BY created_at DESC LIMIT 1)")
			}
		}

		if len(statusConditions) > 0 {
			q = q.Where("("+strings.Join(statusConditions, " OR ")+")", statusValues...)
		}
	}

	if params.LocationRestrict {
		if len(params.LocationIDs) == 0 {
			q = q.Where("1 = 0")
		} else {
			q = q.Where(
				`EXISTS (
					SELECT 1 FROM project_flocks pf
					WHERE pf.id = laying_transfers.from_project_flock_id
						AND pf.location_id IN ?
				) OR EXISTS (
					SELECT 1 FROM project_flocks pf
					WHERE pf.id = laying_transfers.to_project_flock_id
						AND pf.location_id IN ?
				)`,
				params.LocationIDs, params.LocationIDs,
			)
		}
	}

	if err := q.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	q = q.Offset(offset).Limit(limit).
		Preload("FromProjectFlock").
		Preload("ToProjectFlock").
		Preload("SourceProjectFlockKandang").
		Preload("SourceProjectFlockKandang.Kandang").
		Preload("SourceProductWarehouse").
		Preload("CreatedUser").
		Preload("ExecutedUser").
		Preload("Sources").
		Preload("Sources.SourceProjectFlockKandang").
		Preload("Sources.SourceProjectFlockKandang.Kandang").
		Preload("Sources.ProductWarehouse").
		Preload("Targets").
		Preload("Targets.TargetProjectFlockKandang").
		Preload("Targets.TargetProjectFlockKandang.Kandang").
		Preload("Targets.ProductWarehouse").
		Order("laying_transfers.created_at DESC")

	if err := q.Find(&records).Error; err != nil {
		return nil, 0, err
	}

	return records, total, nil
}

func (r *TransferLayingRepositoryImpl) GetLatestApprovedBySourceKandang(ctx context.Context, sourceProjectFlockKandangID uint) (*entity.LayingTransfer, error) {
	if sourceProjectFlockKandangID == 0 {
		return nil, gorm.ErrRecordNotFound
	}

	var transfer entity.LayingTransfer
	err := r.db.WithContext(ctx).
		Model(&entity.LayingTransfer{}).
		Distinct("laying_transfers.*").
		Joins("LEFT JOIN laying_transfer_sources lts ON lts.laying_transfer_id = laying_transfers.id AND lts.deleted_at IS NULL").
		Where("(laying_transfers.source_project_flock_kandang_id = ? OR lts.source_project_flock_kandang_id = ?)", sourceProjectFlockKandangID, sourceProjectFlockKandangID).
		Where("laying_transfers.deleted_at IS NULL").
		Where(`(
				SELECT a.action
			FROM approvals a
			WHERE a.approvable_type = ?
			  AND a.approvable_id = laying_transfers.id
			ORDER BY a.id DESC
			LIMIT 1
		) = ?`, string(utils.ApprovalWorkflowTransferToLaying), entity.ApprovalActionApproved).
		Order("laying_transfers.id DESC").
		First(&transfer).Error
	if err != nil {
		return nil, err
	}
	return &transfer, nil
}

func (r *TransferLayingRepositoryImpl) GetLatestApprovedByTargetKandang(ctx context.Context, targetProjectFlockKandangID uint) (*entity.LayingTransfer, error) {
	if targetProjectFlockKandangID == 0 {
		return nil, gorm.ErrRecordNotFound
	}

	var transfer entity.LayingTransfer
	err := r.db.WithContext(ctx).
		Model(&entity.LayingTransfer{}).
		Joins("JOIN laying_transfer_targets ltt ON ltt.laying_transfer_id = laying_transfers.id AND ltt.deleted_at IS NULL").
		Where("ltt.target_project_flock_kandang_id = ?", targetProjectFlockKandangID).
		Where("laying_transfers.deleted_at IS NULL").
		Where(`(
			SELECT a.action
			FROM approvals a
			WHERE a.approvable_type = ?
			  AND a.approvable_id = laying_transfers.id
			ORDER BY a.id DESC
			LIMIT 1
		) = ?`, string(utils.ApprovalWorkflowTransferToLaying), entity.ApprovalActionApproved).
		Order("laying_transfers.id DESC").
		First(&transfer).Error
	if err != nil {
		return nil, err
	}
	return &transfer, nil
}
