package repository

import (
	"context"
	"errors"

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

type NonstockRepository interface {
	repository.BaseRepository[entity.Nonstock]
	NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
	SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, nonstockID uint, suppliers []entity.NonstockSupplier) error
	UomExists(ctx context.Context, uomID uint) (bool, error)
	GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error)
	SyncFlags(ctx context.Context, tx *gorm.DB, nonstockID uint, flags []string) error
	DeleteFlags(ctx context.Context, tx *gorm.DB, nonstockID uint) error
	GetFlags(ctx context.Context, nonstockID uint) ([]entity.Flag, error)
	IdExists(ctx context.Context, id uint) (bool, error)
	IsNonstockAssociatedWithSupplier(ctx context.Context, nonstockID uint, supplierID uint64) (bool, error)
}

type NonstockRepositoryImpl struct {
	*repository.BaseRepositoryImpl[entity.Nonstock]
}

func NewNonstockRepository(db *gorm.DB) NonstockRepository {
	return &NonstockRepositoryImpl{
		BaseRepositoryImpl: repository.NewBaseRepository[entity.Nonstock](db),
	}
}

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

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

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

	if suppliers == nil {
		return db.WithContext(ctx).
			Where("nonstock_id = ?", nonstockID).
			Delete(&entity.NonstockSupplier{}).
			Error
	}

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

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

	incomingMap := make(map[uint]struct{}, len(suppliers))
	for _, rel := range suppliers {
		incomingMap[rel.SupplierId] = struct{}{}
		if _, exists := existingMap[rel.SupplierId]; exists {
			continue
		}
		record := entity.NonstockSupplier{
			NonstockId: nonstockID,
			SupplierId: rel.SupplierId,
		}
		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("nonstock_id = ? AND supplier_id = ?", nonstockID, rel.SupplierId).
				Delete(&entity.NonstockSupplier{}).
				Error; err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					continue
				}
				return err
			}
		}
	}

	return nil
}

func (r *NonstockRepositoryImpl) 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 *NonstockRepositoryImpl) 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 *NonstockRepositoryImpl) SyncFlags(ctx context.Context, tx *gorm.DB, nonstockID 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 = ?", nonstockID, entity.FlagableTypeNonstock).
		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:   nonstockID,
				FlagableType: entity.FlagableTypeNonstock,
			}
		}
		if err := db.WithContext(ctx).Create(&newFlags).Error; err != nil {
			return err
		}
	}

	return nil
}

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

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

func (r *NonstockRepositoryImpl) IsNonstockAssociatedWithSupplier(ctx context.Context, nonstockID uint, supplierID uint64) (bool, error) {
	var count int64
	if err := r.DB().WithContext(ctx).
		Model(&entity.NonstockSupplier{}).
		Where("nonstock_id = ? AND supplier_id = ?", nonstockID, supplierID).
		Count(&count).
		Error; err != nil {
		return false, err
	}
	return count > 0, nil
}
