/*
 * File: src/main/java/purchase/service/GRNService.java
 * Description: This service class manages operations related to Goods Receipt Notes (GRNs)
 * within the purchase module of the ERP system. It provides functionalities to create,
 * update, retrieve, and delete GRNs, along with their associated items (GRNItems).
 * The service integrates with repositories for GRNs, purchases, suppliers, and
 * GRNItems, and handles inventory management by interacting with external APIs.
 * It utilizes JWT for authorization, ensuring that operations respect tenant boundaries
 * by verifying the tenant information included in JWT tokens. Key features include:
 *
 * - Creating new GRNs while validating purchase and supplier information.
 * - Updating existing GRNs and their associated items, including handling inventory updates.
 * - Deleting GRNs with checks for associated purchase returns to maintain data integrity.
 * - Retrieving GRN information with pagination support and filtering options.
 * - Managing associated GRNItems, including creation, update, and deletion of items as necessary.
 *
 * The service encapsulates business logic and communicates with the necessary repositories
 * and external services to maintain a cohesive workflow within the purchase management system.
*/

package com.nebula.erp.purchase.service;

import com.nebula.erp.purchase.model.*;
import com.nebula.erp.purchase.repository.PurchaseRepository;
import com.nebula.erp.purchase.repository.SupplierRepository;
import com.nebula.erp.purchase.repository.GRNRepository;
import com.nebula.erp.purchase.requestmodel.GRNRequest;
import com.nebula.erp.purchase.requestmodel.GRNItemRequest;
import com.nebula.erp.purchase.utility.CreateLogger;
import com.nebula.erp.purchase.utility.JwtRequestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.data.domain.Sort;

@Service
public class GRNService {

    @Autowired
    private GRNRepository grnRepository;

    @Autowired
    private PurchaseRepository purchaseRepository;

    @Autowired
    private SupplierRepository supplierRepository;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private JwtRequestUtils jwtRequestUtils;

    @Value("${add.stock.api}")
    private String addStockAPI;

    @Value("${product.api}")
    private String productApiUrl;

    @Autowired
    private CreateLogger createLogger;

    private static final String path = "/grn";

    public GRN createGRN(GRNRequest grnRequest) {
        String tenantName = jwtRequestUtils.getTenantName();
        String userId = jwtRequestUtils.getUserId();
        HttpHeaders headers = jwtRequestUtils.getAuthorizationHeaders();
        headers.set("Content-Type", "application/json");
        HttpEntity<Void> entity = new HttpEntity<>(headers);

        List<GRNItemRequest> grnItems = grnRequest.getGrn_items();
        if (grnItems == null || grnItems.isEmpty()) {
            throw new IllegalArgumentException("At least one GRN item is required");
        }

        // Fetch Purchase
        Purchase purchase_order = purchaseRepository.findByIdAndTenant(
                grnRequest.getPurchase_order_id(), tenantName)
                .orElseThrow(() -> new ResourceNotFoundException("Purchase not found"));

        // Check if all items in the request are free products (unit_price = 0)
        boolean allFreeProducts = grnItems.stream()
                .allMatch(i -> i.getUnit_price() != null && i.getUnit_price() == 0.0);

        // Block only if PO is completed AND this is NOT a free product GRN
        if ("completed".equalsIgnoreCase(purchase_order.getStatus()) && !allFreeProducts) {
            throw new IllegalArgumentException(
                    "All items for this Purchase Order have already been received.");
        }

        // Fetch Supplier
        Supplier supplier = supplierRepository.findByIdAndTenant(
                grnRequest.getSupplier_id(), tenantName)
                .orElseThrow(() -> new ResourceNotFoundException("Supplier not found"));

        // Check invoice number uniqueness
        if (grnRepository.existsByInvoiceNumber(grnRequest.getInvoice_number(), tenantName)) {
            createLogger.createLogger("error", path, "POST", "Invoice number already exists", "");
            throw new IllegalArgumentException("Invoice number already exists");
        }

        // Build map of PO items: productId -> PurchaseItem
        Map<Long, PurchaseItem> poItemMap = purchase_order.getPurchase_items().stream()
                .collect(Collectors.toMap(
                        PurchaseItem::getProduct_id,
                        item -> item,
                        (existing, replacement) -> existing));

        // Validate each GRN item
        for (GRNItemRequest itemRequest : grnItems) {
            Long productId = itemRequest.getProduct_id();
            boolean isFreeProduct = itemRequest.getUnit_price() != null
                    && itemRequest.getUnit_price() == 0.0;

            // Must belong to this PO regardless of free or regular
            if (!poItemMap.containsKey(productId)) {
                createLogger.createLogger("error", path, "POST",
                        "Product ID " + productId + " is not part of this Purchase Order.", "");
                throw new ResourceNotFoundException(
                        "Product ID " + productId + " is not part of this Purchase Order.");
            }

            // Regular item — block if already received (is_grn = true)
            if (!isFreeProduct && Boolean.TRUE.equals(poItemMap.get(productId).getIs_grn())) {
                createLogger.createLogger("error", path, "POST",
                        "Product ID " + productId + " has already been received in a previous GRN.", "");
                throw new IllegalArgumentException(
                        "Product ID " + productId + " has already been received in a previous GRN.");
            }
        }

        // Create GRN
        GRN grn = new GRN();
        grn.setPurchase_order(purchase_order);
        grn.setSupplier(supplier);
        grn.setDate(grnRequest.getDate());
        grn.setInvoice_number(grnRequest.getInvoice_number());
        grn.setCreated_by(userId);
        grn.setTenant(tenantName);

        // Create GRN Items
        List<GRNItem> grnItemList = grnItems.stream().map(itemRequest -> {
            double unitPrice = itemRequest.getUnit_price() != null
                    ? itemRequest.getUnit_price()
                    : 0.0;
            double taxRate = 0.0;
            double totalPrice = 0.0;

            // Fetch tax rate only for non-free products
            if (unitPrice > 0) {
                try {
                    Map<String, Object> productData = restTemplate
                            .exchange(productApiUrl + itemRequest.getProduct_id(),
                                    HttpMethod.GET, entity, Map.class)
                            .getBody();
                    List<Map<String, Object>> products = (List<Map<String, Object>>) ((Map<String, Object>) productData
                            .get("data")).get("items");

                    if (products == null || products.isEmpty()) {
                        throw new ResourceNotFoundException(
                                "Product not found with id: " + itemRequest.getProduct_id());
                    }

                    Object rate = products.get(0).get("tax_rate");
                    if (rate != null) {
                        taxRate = Double.parseDouble(String.valueOf(rate));
                    }
                } catch (HttpClientErrorException | HttpServerErrorException ex) {
                    createLogger.createLogger("error", path, "POST", ex.getMessage(), "");
                    throw ex;
                }
            }

            double subtotal = unitPrice * itemRequest.getQuantity();
            double taxAmount = subtotal * taxRate / 100;
            totalPrice = subtotal + taxAmount;

            GRNItem grnItem = new GRNItem();
            grnItem.setProduct_id(itemRequest.getProduct_id());
            grnItem.setQuantity(itemRequest.getQuantity());
            grnItem.setUnit_price(unitPrice);
            grnItem.setTotal_price(totalPrice);
            grnItem.setExpiration_date(itemRequest.getExpiration_date());
            grnItem.setManufacture_date(itemRequest.getManufacture_date());
            grnItem.setBatch_code(itemRequest.getBatch_code());
            grnItem.setTax_id(itemRequest.getTax_id());
            grnItem.setCreated_by(userId);
            grnItem.setTenant(tenantName);
            grnItem.setGrn_item(grn);
            return grnItem;
        }).collect(Collectors.toList());

        grn.setGrn_items(grnItemList);
        GRN savedGRN = grnRepository.save(grn);

        // ── Mark is_grn = true only for regular (non-free) PO items ─────────────
        grnItems.stream()
                .filter(i -> i.getUnit_price() != null && i.getUnit_price() > 0)
                .forEach(i -> {
                    PurchaseItem poItem = poItemMap.get(i.getProduct_id());
                    if (poItem != null) {
                        poItem.setIs_grn(true);
                        poItem.setUpdated_at(Instant.now());
                    }
                });

        // ── Auto-update PO status ────────────────────────────────────────────────
        // If ALL PO items have is_grn = true → completed, else → partially_received
        boolean allReceived = purchase_order.getPurchase_items()
                .stream()
                .allMatch(item -> Boolean.TRUE.equals(item.getIs_grn()));

        purchase_order.setStatus(allReceived ? "completed" : "partially_received");
        purchase_order.setUpdated_at(Instant.now());
        purchaseRepository.saveAndFlush(purchase_order);

        savedGRN.setPurchase_order(
                purchaseRepository.findById(purchase_order.getId())
                        .orElseThrow(() -> new RuntimeException("Purchase not found after update")));

        // ── Post to inventory API ────────────────────────────────────────────────
        grnItemList.forEach(grnItem -> {
            Map<String, Object> stockData = new HashMap<>();
            stockData.put("product_id", grnItem.getProduct_id());
            stockData.put("quantity", grnItem.getQuantity());
            stockData.put("batch_code", grnItem.getBatch_code());
            stockData.put("expiry_date", grnItem.getExpiration_date());
            stockData.put("manufacture_date", grnItem.getManufacture_date());
            stockData.put("grn_item_id", grnItem.getId());
            stockData.put("reason", grnItem.getUnit_price() == 0 ? "BONUS" : "PURCHASE");

            HttpEntity<Map<String, Object>> stockEntity = new HttpEntity<>(stockData, headers);
            restTemplate.exchange(addStockAPI, HttpMethod.POST, stockEntity, String.class);
        });

        return savedGRN;
    }

    public Optional<GRN> getGRN(Long id) {
        String tenantName = jwtRequestUtils.getTenantName();
        return grnRepository.findByIdAndTenant(id, tenantName);
    }

    public Page<GRN> getAllGRN(
            int page,
            int size,
            Boolean purchase_return,
            Integer purchaseOrderId,
            String search) {

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

        String tenantName = jwtRequestUtils.getTenantName();

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

        // PURCHASE RETURN FILTER
        if (Boolean.TRUE.equals(purchase_return)) {
            return hasSearch
                    ? grnRepository.searchWithPurchaseReturn(tenantName, keyword, pageable)
                    : grnRepository.findAllWithPurchaseReturn(tenantName, pageable);
        }

        if (Boolean.FALSE.equals(purchase_return)) {
            return hasSearch
                    ? grnRepository.searchWithoutPurchaseReturn(tenantName, keyword, pageable)
                    : grnRepository.findAllWithoutPurchaseReturn(tenantName, pageable);
        }

        // DEFAULT
        return hasSearch
                ? grnRepository.searchAllGRN(tenantName, keyword, pageable)
                : grnRepository.findAllByTenant(pageable, tenantName);
    }
}