package repository

import (
	"context"

	"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gorm.io/gorm"
)

type ProductRepository interface {
	repository.BaseRepository[entity.Product]
	NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
	SkuExists(ctx context.Context, sku string, excludeID *uint) (bool, error)
	UomExists(ctx context.Context, uomID uint) (bool, error)
	IdExists(ctx context.Context, id uint) (bool, error)
	CategoryExists(ctx context.Context, categoryID uint) (bool, error)
	GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error)
	IsLinkedToSupplier(ctx context.Context, productID, supplierID uint) (bool, error)
	SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, suppliers []entity.ProductSupplier) error
	SyncFlags(ctx context.Context, tx *gorm.DB, productID uint, flags []string) error
	DeleteFlags(ctx context.Context, tx *gorm.DB, productID uint) error
	GetFlags(ctx context.Context, productID uint) ([]entity.Flag, error)
}

type ProductRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.Product]
}

func NewProductRepository(db *gorm.DB) ProductRepository {
	return &ProductRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.Product](db),
	}
}

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

func (r *ProductRepositoryImpl) SkuExists(ctx context.Context, sku string, excludeID *uint) (bool, error) {
	var count int64
	q := r.DB().WithContext(ctx).
		Model(new(entity.Product)).
		Where("sku = ?", sku).
		Where("deleted_at IS NULL")
	if excludeID != nil {
		q = q.Where("id <> ?", *excludeID)
	}
	if err := q.Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *ProductRepositoryImpl) UomExists(ctx context.Context, uomID uint) (bool, error) {
	var count int64
	if err := r.DB().WithContext(ctx).
		Model(&entity.Uom{}).
		Where("id = ?", uomID).
		Count(&count).
		Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *ProductRepositoryImpl) CategoryExists(ctx context.Context, categoryID uint) (bool, error) {
	var count int64
	if err := r.DB().WithContext(ctx).
		Model(&entity.ProductCategory{}).
		Where("id = ?", categoryID).
		Count(&count).
		Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *ProductRepositoryImpl) GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error) {
	if len(supplierIDs) == 0 {
		return nil, nil
	}
	var suppliers []entity.Supplier
	if err := r.DB().WithContext(ctx).
		Select("id", "category").
		Where("id IN ?", supplierIDs).
		Find(&suppliers).
		Error; err != nil {
		return nil, err
	}
	return suppliers, nil
}

func (r *ProductRepositoryImpl) IsLinkedToSupplier(ctx context.Context, productID, supplierID uint) (bool, error) {
	var count int64
	if err := r.DB().WithContext(ctx).
		Model(&entity.ProductSupplier{}).
		Where("product_id = ? AND supplier_id = ?", productID, supplierID).
		Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *ProductRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, suppliers []entity.ProductSupplier) error {
	db := tx
	if db == nil {
		db = r.DB()
	}

	if suppliers == nil {
		return db.WithContext(ctx).
			Where("product_id = ?", productID).
			Delete(&entity.ProductSupplier{}).
			Error
	}

	var existing []entity.ProductSupplier
	if err := db.WithContext(ctx).
		Where("product_id = ?", productID).
		Find(&existing).
		Error; err != nil {
		return err
	}

	existingMap := make(map[uint]entity.ProductSupplier, len(existing))
	for _, rel := range existing {
		existingMap[rel.SupplierId] = rel
	}

	incomingMap := make(map[uint]struct{}, len(suppliers))
	for _, rel := range suppliers {
		incomingMap[rel.SupplierId] = struct{}{}
		if existingRel, exists := existingMap[rel.SupplierId]; exists {
			if existingRel.Price != rel.Price {
				if err := db.WithContext(ctx).
					Model(&entity.ProductSupplier{}).
					Where("product_id = ? AND supplier_id = ?", productID, rel.SupplierId).
					Update("price", rel.Price).
					Error; err != nil {
					return err
				}
			}
			continue
		}
		record := entity.ProductSupplier{
			ProductId:  productID,
			SupplierId: rel.SupplierId,
			Price:      rel.Price,
		}
		if err := db.WithContext(ctx).Create(&record).Error; err != nil {
			return err
		}
	}

	for _, rel := range existing {
		if _, keep := incomingMap[rel.SupplierId]; !keep {
			if err := db.WithContext(ctx).
				Where("product_id = ? AND supplier_id = ?", productID, rel.SupplierId).
				Delete(&entity.ProductSupplier{}).
				Error; err != nil {
				return err
			}
		}
	}

	return nil
}

func (r *ProductRepositoryImpl) SyncFlags(ctx context.Context, tx *gorm.DB, productID uint, flags []string) error {
	db := tx
	if db == nil {
		db = r.DB()
	}

	// Hapus flags lama terlebih dahulu
	if err := db.WithContext(ctx).
		Where("flagable_id = ? AND flagable_type = ?", productID, entity.FlagableTypeProduct).
		Delete(&entity.Flag{}).
		Error; err != nil {
		return err
	}

	// Insert flags baru jika ada
	if len(flags) > 0 {
		newFlags := make([]entity.Flag, len(flags))
		for i, f := range flags {
			newFlags[i] = entity.Flag{
				Name:         f,
				FlagableID:   productID,
				FlagableType: entity.FlagableTypeProduct,
			}
		}
		if err := db.WithContext(ctx).Create(&newFlags).Error; err != nil {
			return err
		}
	}

	return nil
}

func (r *ProductRepositoryImpl) DeleteFlags(ctx context.Context, tx *gorm.DB, productID uint) error {
	db := tx
	if db == nil {
		db = r.DB()
	}
	return db.WithContext(ctx).
		Where("flagable_id = ? AND flagable_type = ?", productID, entity.FlagableTypeProduct).
		Delete(&entity.Flag{}).
		Error
}

func (r *ProductRepositoryImpl) GetFlags(ctx context.Context, productID uint) ([]entity.Flag, error) {
	var flags []entity.Flag
	if err := r.DB().WithContext(ctx).
		Where("flagable_id = ? AND flagable_type = ?", productID, entity.FlagableTypeProduct).
		Find(&flags).
		Error; err != nil {
		return nil, err
	}
	return flags, nil
}

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