InventoryItemValidationHelper.java

package com.smartsupplypro.inventory.service.impl.inventory;

import java.time.LocalDateTime;
import java.util.UUID;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import com.smartsupplypro.inventory.dto.InventoryItemDTO;
import com.smartsupplypro.inventory.model.InventoryItem;
import com.smartsupplypro.inventory.repository.InventoryItemRepository;
import com.smartsupplypro.inventory.repository.SupplierRepository;
import com.smartsupplypro.inventory.validation.InventoryItemSecurityValidator;
import com.smartsupplypro.inventory.validation.InventoryItemValidator;

import lombok.RequiredArgsConstructor;

/**
 * Validation helper for inventory item operations.
 *
 * <p>Centralizes validation logic including:
 * <ul>
 *   <li>Base field validation (name, price, quantity)</li>
 *   <li>Uniqueness checks (name + price combinations)</li>
 *   <li>Supplier existence validation</li>
 *   <li>Security permission checks</li>
 *   <li>Server-side field population (ID, createdBy, timestamps)</li>
 * </ul>
 *
 * @author Smart Supply Pro Development Team
 * @version 1.0.0
 * @since 2.0.0
 */
@Component
@RequiredArgsConstructor
public class InventoryItemValidationHelper {

    private final InventoryItemRepository repository;
    private final SupplierRepository supplierRepository;

    /**
     * Validates and prepares DTO for item creation.
     *
     * <p>Steps: Populate createdBy → validate base fields → check uniqueness → validate supplier.
     *
     * @param dto the inventory item DTO to validate
     * @throws IllegalArgumentException if validation fails
     */
    public void validateForCreation(InventoryItemDTO dto) {
        // Populate createdBy from authenticated user
        if (dto.getCreatedBy() == null || dto.getCreatedBy().trim().isEmpty()) {
            dto.setCreatedBy(currentUsername());
        }
        
        // Validate base fields
        InventoryItemValidator.validateBase(dto);
        
        // Check uniqueness (name + price)
        InventoryItemValidator.validateInventoryItemNotExists(dto.getName(), dto.getPrice(), repository);
        
        // Validate supplier exists
        validateSupplierExists(dto.getSupplierId());
    }

    /**
     * Validates and prepares entity for creation with server-side fields.
     *
     * <p>Generates: ID, createdBy, createdAt, minimum quantity default.
     *
     * @param entity the inventory item entity to populate
     */
    public void populateServerFields(InventoryItem entity) {
        // Generate UUID if not provided
        if (entity.getId() == null || entity.getId().isBlank()) {
            entity.setId(UUID.randomUUID().toString());
        }
        
        // Always set createdBy from SecurityContext (authoritative source)
        entity.setCreatedBy(currentUsername());
        
        // Set createdAt timestamp
        if (entity.getCreatedAt() == null) {
            entity.setCreatedAt(LocalDateTime.now());
        }
        
        // Apply default minimum quantity
        if (entity.getMinimumQuantity() <= 0) {
            entity.setMinimumQuantity(10);
        }
    }

    /**
     * Validates DTO for item update.
     *
     * <p>Steps: Validate base fields → validate supplier → check existence → check permissions.
     *
     * @param id the item ID being updated
     * @param dto the updated inventory item data
     * @return the existing item entity
     * @throws IllegalArgumentException if validation fails
     */
    public InventoryItem validateForUpdate(String id, InventoryItemDTO dto) {
        // Validate base fields
        InventoryItemValidator.validateBase(dto);
        
        // Validate supplier exists
        validateSupplierExists(dto.getSupplierId());

        // Verify item exists
        InventoryItem existing = InventoryItemValidator.validateExists(id, repository);

        // Check user permissions
        InventoryItemSecurityValidator.validateUpdatePermissions(existing, dto);

        return existing;
    }

    /**
     * Validates uniqueness if name or price changed during update.
     *
     * @param id the item ID being updated
     * @param existing the existing item entity
     * @param dto the updated item data
     */
    public void validateUniquenessOnUpdate(String id, InventoryItem existing, InventoryItemDTO dto) {
        boolean nameChanged  = !existing.getName().equalsIgnoreCase(dto.getName());
        boolean priceChanged = !existing.getPrice().equals(dto.getPrice());
        
        if (nameChanged || priceChanged) {
            InventoryItemValidator.validateInventoryItemNotExists(id, dto.getName(), dto.getPrice(), repository);
        }
    }

    /**
     * Validates item exists and retrieves it.
     *
     * @param id the item identifier
     * @return the existing item entity
     * @throws IllegalArgumentException if item not found
     */
    public InventoryItem validateExists(String id) {
        return repository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Item not found"));
    }

    /**
     * Validates item for deletion.
     *
     * <p>Steps: Validate item exists → check quantity is zero.
     *
     * @param id the item ID to validate for deletion
     * @throws IllegalArgumentException if item not found
     * @throws IllegalStateException if quantity is greater than zero
     */
    public void validateForDeletion(String id) {
        InventoryItem item = InventoryItemValidator.validateExists(id, repository);
        InventoryItemValidator.assertQuantityIsZeroForDeletion(item);
    }

    /**
     * Validates that the specified supplier exists.
     *
     * @param supplierId the supplier identifier
     * @throws IllegalArgumentException if supplier does not exist
     */
    private void validateSupplierExists(String supplierId) {
        if (!supplierRepository.existsById(supplierId)) {
            throw new IllegalArgumentException("Supplier does not exist");
        }
    }

    /**
     * Retrieves current authenticated username from Spring Security context.
     *
     * @return authenticated username, or "system" if no authentication present
     */
    private String currentUsername() {
        Authentication a = SecurityContextHolder.getContext() != null
                ? SecurityContextHolder.getContext().getAuthentication() : null;
        return a != null ? a.getName() : "system";
    }
}