package repository

import (
	"context"

	"apskel-pos-be/internal/entities"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type InventoryMovementRepository interface {
	Create(ctx context.Context, movement *entities.InventoryMovement) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error)
	GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error)
	List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.InventoryMovement, int64, error)
	GetByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) ([]*entities.InventoryMovement, int64, error)
	GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.InventoryMovement, error)
	GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.InventoryMovement, error)
	Count(ctx context.Context, filters map[string]interface{}) (int64, error)
	CreateWithTransaction(tx *gorm.DB, movement *entities.InventoryMovement) error
	CreateInBatches(ctx context.Context, movements []*entities.InventoryMovement, batchSize int) error
}

type InventoryMovementRepositoryImpl struct {
	db *gorm.DB
}

func NewInventoryMovementRepositoryImpl(db *gorm.DB) *InventoryMovementRepositoryImpl {
	return &InventoryMovementRepositoryImpl{
		db: db,
	}
}

func (r *InventoryMovementRepositoryImpl) Create(ctx context.Context, movement *entities.InventoryMovement) error {
	return r.db.WithContext(ctx).Create(movement).Error
}

func (r *InventoryMovementRepositoryImpl) CreateWithTransaction(tx *gorm.DB, movement *entities.InventoryMovement) error {
	return tx.Create(movement).Error
}

func (r *InventoryMovementRepositoryImpl) CreateInBatches(ctx context.Context, movements []*entities.InventoryMovement, batchSize int) error {
	if len(movements) == 0 {
		return nil
	}
	return r.db.WithContext(ctx).CreateInBatches(movements, batchSize).Error
}

func (r *InventoryMovementRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error) {
	var movement entities.InventoryMovement
	err := r.db.WithContext(ctx).First(&movement, "id = ?", id).Error
	if err != nil {
		return nil, err
	}
	return &movement, nil
}

func (r *InventoryMovementRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error) {
	var movement entities.InventoryMovement
	err := r.db.WithContext(ctx).
		Preload("Organization").
		Preload("Outlet").
		Preload("Product").
		Preload("Product.Category").
		Preload("Order").
		Preload("Payment").
		Preload("User").
		First(&movement, "id = ?", id).Error
	if err != nil {
		return nil, err
	}
	return &movement, nil
}

func (r *InventoryMovementRepositoryImpl) List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.InventoryMovement, int64, error) {
	var movements []*entities.InventoryMovement
	var total int64

	query := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}).
		Preload("Product").
		Preload("Product.Category").
		Preload("Outlet").
		Preload("Order").
		Preload("Payment").
		Preload("User")

	for key, value := range filters {
		switch key {
		case "search":
			searchValue := "%" + value.(string) + "%"
			query = query.Joins("JOIN products ON inventory_movements.product_id = products.id").
				Where("products.name ILIKE ? OR inventory_movements.reason ILIKE ?", searchValue, searchValue)
		case "date_from":
			query = query.Where("created_at >= ?", value)
		case "date_to":
			query = query.Where("created_at <= ?", value)
		case "movement_type":
			query = query.Where("movement_type = ?", value)
		case "reference_type":
			query = query.Where("reference_type = ?", value)
		default:
			query = query.Where(key+" = ?", value)
		}
	}

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

	err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&movements).Error
	return movements, total, err
}

func (r *InventoryMovementRepositoryImpl) GetByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) ([]*entities.InventoryMovement, int64, error) {
	var movements []*entities.InventoryMovement
	var total int64

	query := r.db.WithContext(ctx).Model(&entities.InventoryMovement{}).
		Preload("Product").
		Preload("Product.Category").
		Preload("Order").
		Preload("Payment").
		Preload("User").
		Where("product_id = ? AND outlet_id = ?", productID, outletID)

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

	err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&movements).Error
	return movements, total, err
}

func (r *InventoryMovementRepositoryImpl) GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.InventoryMovement, error) {
	var movements []*entities.InventoryMovement
	err := r.db.WithContext(ctx).
		Preload("Product").
		Preload("Product.Category").
		Where("order_id = ?", orderID).
		Order("created_at DESC").
		Find(&movements).Error
	return movements, err
}

func (r *InventoryMovementRepositoryImpl) GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.InventoryMovement, error) {
	var movements []*entities.InventoryMovement
	err := r.db.WithContext(ctx).
		Preload("Product").
		Preload("Product.Category").
		Where("payment_id = ?", paymentID).
		Order("created_at DESC").
		Find(&movements).Error
	return movements, err
}

func (r *InventoryMovementRepositoryImpl) GetByReference(ctx context.Context, referenceType entities.InventoryMovementReferenceType, referenceID uuid.UUID) ([]*entities.InventoryMovement, error) {
	var movements []*entities.InventoryMovement
	err := r.db.WithContext(ctx).
		Preload("Product").
		Preload("Product.Category").
		Where("reference_type = ? AND reference_id = ?", referenceType, referenceID).
		Order("created_at DESC").
		Find(&movements).Error
	return movements, err
}

func (r *InventoryMovementRepositoryImpl) Count(ctx context.Context, filters map[string]interface{}) (int64, error) {
	var count int64
	query := r.db.WithContext(ctx).Model(&entities.InventoryMovement{})

	for key, value := range filters {
		switch key {
		case "search":
			searchValue := "%" + value.(string) + "%"
			query = query.Joins("JOIN products ON inventory_movements.product_id = products.id").
				Where("products.name ILIKE ? OR inventory_movements.reason ILIKE ?", searchValue, searchValue)
		case "date_from":
			query = query.Where("created_at >= ?", value)
		case "date_to":
			query = query.Where("created_at <= ?", value)
		case "movement_type":
			query = query.Where("movement_type = ?", value)
		case "reference_type":
			query = query.Where("reference_type = ?", value)
		default:
			query = query.Where(key+" = ?", value)
		}
	}

	err := query.Count(&count).Error
	return count, err
}
