/*
 * File: src/main/java/purchase/service/PurchaseService.java
 * Description: This service class manages operations related to Purchase entities
 * within the ERP purchase module. It provides functionality to create, retrieve,
 * update, and delete purchases while maintaining the integrity of associated
 * purchase items. The service interacts with various repositories to perform
 * CRUD operations and utilizes JWT for authorization checks. Key functionalities include:
 *
 * - **createPurchase**: Validates the incoming PurchaseRequest, ensuring at least one
 *   purchase item is present, fetches the corresponding Supplier, and saves the
 *   Purchase entity along with its items.
 *
 * - **getPurchase**: Retrieves a specific Purchase by its ID, returning an Optional
 *   Purchase object.
 *
 * - **getAllPurchases**: Fetches a paginated list of all Purchases for the tenant,
 *   with options to filter by association with GRNs (Goods Receipt Notes).
 *
 * - **updatePurchase**: Updates an existing Purchase based on the provided
 *   PurchaseRequest, allowing for both updating existing items and adding new ones,
 *   while ensuring the tenant context is respected.
 *
 * - **deletePurchase**: Deletes a Purchase by its ID after validating that the
 *   requesting tenant matches the tenant associated with the Purchase, and it
 *   ensures all associated PurchaseItems are also deleted.
 *
 * The service encapsulates business logic related to Purchase management, ensuring
 * data consistency and adherence to business rules while leveraging Spring's
 * dependency injection for accessing repositories and utilities.
 */

package com.nebula.erp.purchase.service;

import com.nebula.erp.purchase.model.Purchase;
import com.nebula.erp.purchase.model.Supplier;
import com.nebula.erp.purchase.repository.PurchaseItemDetailRepository;
import com.nebula.erp.purchase.repository.PurchaseRepository;
import com.nebula.erp.purchase.repository.SupplierRepository;
import com.nebula.erp.purchase.requestmodel.PurchaseRequest;
import com.nebula.erp.purchase.requestmodel.PurchaseItemRequest;
import com.nebula.erp.purchase.utility.CreateLogger;
import com.nebula.erp.purchase.utility.JwtRequestUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.stereotype.Service;
import java.util.stream.Collectors;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import com.nebula.erp.purchase.model.PurchaseItem;
import org.springframework.data.domain.Sort;

@Service
public class PurchaseService {

    @Autowired
    private PurchaseRepository purchaseRepository;

    @Autowired
    private SupplierRepository supplierRepository;

    @Autowired
    private PurchaseItemDetailRepository purchaseItemDetailRepository;

    @Autowired
    private JwtRequestUtils jwtRequestUtils;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private CreateLogger createLogger;

    private static final String path = "/purchases";

    public Purchase createPurchase(PurchaseRequest purchaseRequest) {
        // *** while creating purchase have to check product is exist ***
        String tenantName = jwtRequestUtils.getTenantName();
        String userId = jwtRequestUtils.getUserId();

        // Check if there is at least one purchase item
        List<PurchaseItemRequest> purchaseItems = purchaseRequest.getPurchase_items();
        if (purchaseItems == null || purchaseItems.isEmpty()) {
            createLogger.createLogger("error", path, "POST", "At least one purchase item is required", "");
            throw new IllegalArgumentException("At least one purchase item is required");
        }

        // Fetch Supplier
        Supplier supplier = supplierRepository.findById(purchaseRequest.getSupplier_id())
                .orElseThrow(() -> new IllegalArgumentException("Supplier not found"));

        Purchase purchase = new Purchase();
        purchase.setSupplier(supplier);
        purchase.setDate(purchaseRequest.getDate());
        purchase.setStatus(purchaseRequest.getStatus());
        purchase.setRemarks(purchaseRequest.getRemarks());
        purchase.setCreated_by(userId);
        purchase.setTenant(tenantName);

        // Create Purchase Items
        List<PurchaseItem> items = purchaseItems.stream().map(itemRequest -> {
            PurchaseItem item = new PurchaseItem();
            item.setProduct_id(itemRequest.getProduct_id());
            item.setQuantity(itemRequest.getQuantity());
            item.setCreated_by(userId);
            item.setTenant(tenantName);
            item.setPurchase_order(purchase);
            return item;
        }).collect(Collectors.toList());

        purchase.setPurchase_items(items);
        purchaseRepository.save(purchase);

        return purchase;
    }

    // Get purchases count
    public Integer getPurchasesCount(String tenant_name) {
        return purchaseRepository.findCountByTenant(tenant_name);
    }

    public Optional<Purchase> getPurchase(Long id) {
        return purchaseRepository.findById(id);
    }

    public Page<Purchase> getAllPurchases(int page, int size, Boolean grn, String search) {

        Pageable pageable = PageRequest.of(
                page - 1,
                size,
                Sort.by(Sort.Direction.DESC, "created_at"));

        String tenant = jwtRequestUtils.getTenantName();

        boolean hasSearch = search != null && !search.trim().isEmpty();
        String keyword = hasSearch ? "%" + search.trim().toLowerCase() + "%" : null;

        if (Boolean.TRUE.equals(grn)) {
            return hasSearch
                    ? purchaseRepository.searchPurchases(tenant, keyword, pageable)
                    : purchaseRepository.findAllWithGRN(tenant, pageable);
        }

        if (Boolean.FALSE.equals(grn)) {
            return hasSearch
                    ? purchaseRepository.searchPurchases(tenant, keyword, pageable)
                    : purchaseRepository.findAllWithoutGRN(tenant, pageable);
        }

        return hasSearch
                ? purchaseRepository.searchPurchases(tenant, keyword, pageable)
                : purchaseRepository.findAllByTenant(tenant, pageable);
    }

    public Purchase updatePurchase(Long id, PurchaseRequest purchaseRequest) {
        String tenantName = jwtRequestUtils.getTenantName();
        String userId = jwtRequestUtils.getUserId();

        // Fetch the existing Purchase entity
        Purchase purchase = purchaseRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Purchase not found"));

        // Check if the tenant name matches
        if (!purchase.getTenant().equals(tenantName)) {
            createLogger.createLogger("error", path, "PUT", "Unauthorized to update this purchase; tenant mismatch",
                    "");
            throw new RuntimeException("Unauthorized to update this purchase; tenant mismatch");
        }

        // Fetch Supplier
        Supplier supplier = supplierRepository.findById(purchaseRequest.getSupplier_id())
                .orElseThrow(() -> new IllegalArgumentException("Supplier not found"));

        // Update purchase details
        purchase.setSupplier(supplier);
        purchase.setDate(purchaseRequest.getDate());
        purchase.setStatus(purchaseRequest.getStatus());
        purchase.setRemarks(purchaseRequest.getRemarks());
        purchase.setCreated_by(userId);
        purchase.setUpdated_at(Instant.now());
        purchase.setTenant(tenantName);

        // Fetch existing purchase items
        List<PurchaseItem> existingItems = new ArrayList<>(purchase.getPurchase_items());

        // Map to track items by ID
        Map<Long, PurchaseItem> itemMap = existingItems.stream()
                .collect(Collectors.toMap(PurchaseItem::getId, item -> item));

        // List to hold updated items
        List<PurchaseItem> updatedItems = new ArrayList<>();

        // Process incoming items
        for (PurchaseItemRequest itemRequest : purchaseRequest.getPurchase_items()) {
            PurchaseItem item;

            if (itemRequest.getId() != null) {
                // Update existing item
                item = itemMap.get(itemRequest.getId());

                if (item == null) {
                    createLogger.createLogger("error", path, "POST", "PurchaseItem not found", "");
                    throw new RuntimeException("PurchaseItem not found");
                }

                item.setProduct_id(itemRequest.getProduct_id());
                item.setQuantity(itemRequest.getQuantity());
                item.setCreated_by(userId);
                item.setTenant(tenantName);
            } else {
                // Create new item
                item = new PurchaseItem();
                item.setProduct_id(itemRequest.getProduct_id());
                item.setQuantity(itemRequest.getQuantity());
                item.setCreated_by(userId);
                item.setTenant(tenantName);
            }

            item.setPurchase_order(purchase); // Set the owning Purchase
            updatedItems.add(item);
        }

        // Remove items not present in the updated list
        purchase.getPurchase_items().clear();
        purchase.getPurchase_items().addAll(updatedItems);

        // Save the updated Purchase entity and its items
        // Ensure that all entities are managed properly
        for (PurchaseItem item : updatedItems) {
            if (item.getId() != null) {
                // Merge existing item to ensure the ID is preserved
                purchaseItemDetailRepository.save(item);
            }
        }

        return purchaseRepository.save(purchase);
    }

    public void deletePurchase(Long id) {

        String tenantName = jwtRequestUtils.getTenantName();

        // Check if the purchase exists
        Purchase purchase = purchaseRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Purchase not found"));

        // Check if the tenantName matches the tenant field in the purchase
        if (!purchase.getTenant().equals(tenantName)) {
            createLogger.createLogger("error", path, "DELETE", "Unauthorized to delete this purchase; tenant mismatch",
                    "");
            throw new IllegalArgumentException("Unauthorized to delete this purchase; tenant mismatch");
        }

        // Explicitly delete associated purchase items
        for (PurchaseItem item : purchase.getPurchase_items()) {
            purchaseItemDetailRepository.deleteById(item.getId());
        }

        // Finally, delete the purchase itself
        purchaseRepository.deleteById(id);
    }
}