package repository

import (
	"context"
	"fmt"

	"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/repports/validations"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	"gorm.io/gorm"
)

type ExpenseRealizationRepository interface {
	repository.BaseRepository[entity.ExpenseRealization]
	IdExists(ctx context.Context, id uint64) (bool, error)
	GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error)
	GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error)
	GetClosingOverhead(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]entity.ExpenseRealization, error)
	GetAllWithFilters(ctx context.Context, offset, limit int, filters *validation.ExpenseQuery) ([]entity.ExpenseRealization, int64, error)
}

type ExpenseRealizationRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.ExpenseRealization]
}

func NewExpenseRealizationRepository(db *gorm.DB) ExpenseRealizationRepository {
	return &ExpenseRealizationRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.ExpenseRealization](db),
	}
}

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

func (r *ExpenseRealizationRepositoryImpl) GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error) {
	var realization entity.ExpenseRealization
	err := r.DB().WithContext(ctx).Where("expense_nonstock_id = ?", expenseNonstockID).First(&realization).Error
	return &realization, err
}

func (r *ExpenseRealizationRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error) {
	var realizations []entity.ExpenseRealization
	err := r.DB().WithContext(ctx).
		Preload("ExpenseNonstock").
		Preload("ExpenseNonstock.Nonstock").
		Preload("ExpenseNonstock.Nonstock.Uom").
		Preload("ExpenseNonstock.Nonstock.Flags").
		Preload("ExpenseNonstock.Expense").
		Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
		Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
		Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = expense_nonstocks.project_flock_kandang_id").
		Joins("LEFT JOIN kandangs ON kandangs.id = expense_nonstocks.kandang_id").
		Where("project_flock_kandangs.project_flock_id = ? OR kandangs.id IN (SELECT kandang_id FROM project_flock_kandangs WHERE project_flock_id = ?)", projectFlockID, projectFlockID).
		Find(&realizations).Error
	return realizations, err
}

func (r *ExpenseRealizationRepositoryImpl) GetClosingOverhead(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]entity.ExpenseRealization, error) {
	var realizations []entity.ExpenseRealization

	db := r.DB().WithContext(ctx).
		Preload("ExpenseNonstock").
		Preload("ExpenseNonstock.Nonstock").
		Preload("ExpenseNonstock.Nonstock.Uom").
		Preload("ExpenseNonstock.Nonstock.Flags").
		Preload("ExpenseNonstock.Expense").
		Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
		Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
		Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = expense_nonstocks.project_flock_kandang_id").
		Where("expenses.realization_date IS NOT NULL").
		Where("expenses.category = ?", "BOP")

	if projectFlockKandangID != nil {
		db = db.Where(`(
			expense_nonstocks.project_flock_kandang_id = ? OR
			(expenses.project_flock_id IS NOT NULL AND expenses.project_flock_id::jsonb @> ?::jsonb)
		)`, *projectFlockKandangID, fmt.Sprintf("[%d]", projectFlockID))
	} else {
		db = db.Where(`(
			project_flock_kandangs.project_flock_id = ? OR
			(expenses.project_flock_id IS NOT NULL AND expenses.project_flock_id::jsonb @> ?::jsonb)
		)`, projectFlockID, fmt.Sprintf("[%d]", projectFlockID))
	}

	err := db.Find(&realizations).Error
	return realizations, err
}

func (r *ExpenseRealizationRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, filters *validation.ExpenseQuery) ([]entity.ExpenseRealization, int64, error) {
	var realizations []entity.ExpenseRealization
	var total int64

	db := r.DB().WithContext(ctx).
		Model(&entity.ExpenseRealization{}).
		Preload("ExpenseNonstock", func(db *gorm.DB) *gorm.DB {
			return db.
				Preload("Expense").
				Preload("Expense.Supplier").
				Preload("Kandang").
				Preload("Kandang.Location").
				Preload("Nonstock").
				Preload("Nonstock.Flags")
		}).
		Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
		Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
		Joins("LEFT JOIN suppliers ON suppliers.id = expenses.supplier_id")

	if filters.Search != "" {
		db = db.Where("expenses.category ILIKE ? OR expenses.reference_number ILIKE ? OR expenses.po_number ILIKE ? OR expenses.notes ILIKE ? OR suppliers.name ILIKE ?",
			"%"+filters.Search+"%", "%"+filters.Search+"%", "%"+filters.Search+"%", "%"+filters.Search+"%", "%"+filters.Search+"%")
	}

	if filters.Category != "" {
		db = db.Where("expenses.category = ?", filters.Category)
	}

	if filters.SupplierId > 0 {
		db = db.Where("expenses.supplier_id = ?", filters.SupplierId)
	}

	if filters.KandangId > 0 {
		db = db.Where("expense_nonstocks.kandang_id = ?", filters.KandangId)
	}

	if filters.ProjectFlockKandangId > 0 {
		db = db.Where("expense_nonstocks.project_flock_kandang_id = ?", filters.ProjectFlockKandangId)
	}

	if filters.NonstockId > 0 {
		db = db.Where("expense_nonstocks.nonstock_id = ?", filters.NonstockId)
	}

	locationID := filters.LocationId
	areaID := filters.AreaId

	if filters.AllowedLocationIDs != nil || filters.AllowedAreaIDs != nil || locationID > 0 || areaID > 0 {
		db = db.Joins("JOIN kandangs ON kandangs.id = expense_nonstocks.kandang_id")
	}

	if filters.AllowedLocationIDs != nil {
		if len(filters.AllowedLocationIDs) == 0 {
			db = db.Where("1 = 0")
		} else {
			db = db.Where("kandangs.location_id IN ?", filters.AllowedLocationIDs)
		}
	}

	if filters.AllowedAreaIDs != nil {
		if len(filters.AllowedAreaIDs) == 0 {
			db = db.Where("1 = 0")
		} else {
			db = db.Joins("JOIN locations ON locations.id = kandangs.location_id").
				Where("locations.area_id IN ?", filters.AllowedAreaIDs)
		}
	}

	if locationID > 0 || areaID > 0 {
		if locationID > 0 {
			db = db.Where("kandangs.location_id = ?", uint(locationID))
		}

		if areaID > 0 {
			db = db.Joins("JOIN locations ON locations.id = kandangs.location_id").
				Where("locations.area_id = ?", uint(areaID))
		}
	}

	if filters.RealizationDate != "" {
		if realizationDate, err := utils.ParseDateString(filters.RealizationDate); err == nil {
			db = db.Where("DATE(expenses.realization_date) = ?", realizationDate)
		}
	}

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

	if err := db.
		Offset(offset).
		Limit(limit).
		Order("expense_realizations.created_at DESC").
		Find(&realizations).Error; err != nil {
		return nil, 0, err
	}

	return realizations, total, nil
}
