package com.nebula.erp.reports.service;

import com.nebula.erp.reports.model.purchase.GRNItem;
import com.nebula.erp.reports.model.purchase.Purchase;
import com.nebula.erp.reports.model.sales.PrescriptionItem;
import com.nebula.erp.reports.model.sales.Prescriptions;
import com.nebula.erp.reports.model.sales.SalesItem;
import com.nebula.erp.reports.repository.inventories.InventoryRepository;
import com.nebula.erp.reports.repository.product.BrandRepository;
import com.nebula.erp.reports.repository.product.CategoryRepository;
import com.nebula.erp.reports.repository.product.ProductRepository;
import com.nebula.erp.reports.repository.purchase.GRNItemRepository;
import com.nebula.erp.reports.repository.purchase.PurchaseRepository;
import com.nebula.erp.reports.repository.sales.PrescriptionItemRepository;
import com.nebula.erp.reports.repository.sales.PrescriptionsRepository;
import com.nebula.erp.reports.repository.sales.SalesItemRepository;
import com.nebula.erp.reports.repository.sales.SalesRepository;
import com.nebula.erp.reports.requestmodel.SalesRequest;
import com.nebula.erp.reports.utility.ApiResponseStructure;
import com.nebula.erp.reports.utility.CreateLogger;
import com.nebula.erp.reports.utility.JwtRequestUtils;
import jakarta.servlet.http.HttpServletRequest;
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.jpa.domain.Specification;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class AuditComplianceService {

    @Autowired
    private GRNItemRepository grnItemRepository;

    @Autowired
    private SalesRepository salesRepository;

    @Autowired
    private SalesItemRepository salesItemRepository;

    @Autowired
    private CategoryRepository categoryRepository;

    @Autowired
    private BrandRepository brandRepository;

    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private PrescriptionsRepository prescriptionsRepository;

    @Autowired
    private PrescriptionItemRepository prescriptionItemRepository;

    @Autowired
    private InventoryRepository inventoryRepository;

    @Autowired
    private PurchaseRepository purchaseRepository;

    @Autowired
    private JwtRequestUtils jwtRequestUtils;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private RestTemplate restTemplate;

    @Value("${encounter.api}")
    private String encounterApiUrl;

    @Autowired
    private CreateLogger createLogger;

    private static final String path = "/reports/audit-compliance";

    public ApiResponseStructure<Map<String, Object>> getKPIData(LocalDate fromDate, LocalDate toDate) {
        // Extract Authorization headers from the request
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.AUTHORIZATION, httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION));

        String tenantName = jwtRequestUtils.getTenantNameFromHeaders(headers);

        if (fromDate != null && toDate != null) {

            // Convert LocalDate to LocalDateTime for comparison with created_at
            LocalDateTime fromDateTime = fromDate.atStartOfDay();
            LocalDateTime toDateTime = toDate.atTime(23, 59, 59);
            // Initial values
            Double totalPurchaseOrderCompliance = 0.00;
            Double totalPrescriptionCompliance = 0.00;

            List<Map<String, Object>> discrepancies = new ArrayList<>();
            List<Object[]> inventoryList = inventoryRepository.findAllInventoriesByDateRange(fromDateTime, toDateTime, tenantName);

            inventoryList.forEach(inventory -> {
                Long productId = Long.valueOf(inventory[0].toString());
                Integer actualStock = Integer.parseInt(inventory[1].toString());

                // Calculate expected stock from purchases and sales
                List<GRNItem> purchasedStockItems = grnItemRepository.getTotalPurchasedQuantity(fromDateTime, toDateTime, tenantName, productId);
                List<SalesItem> soldStockItems = salesItemRepository.findSalesItemsByDateRangeAndProductId(fromDateTime, toDateTime, tenantName, productId);

                // Calculate total purchased and sold quantities
                Integer purchasedStock = purchasedStockItems.stream().mapToInt(GRNItem::getQuantity).sum();
                Integer soldStock = soldStockItems.stream().mapToInt(SalesItem::getQuantity).sum();
                Integer expectedStock = purchasedStock - soldStock;

                if (!actualStock.equals(expectedStock)) {
                    Map<String, Object> discrepancy = new LinkedHashMap<>();
                    discrepancy.put("product_id", productId);
                    discrepancy.put("actual_stock", actualStock);
                    discrepancy.put("expected_stock", expectedStock);
                    discrepancy.put("discrepancy", actualStock - expectedStock);
                    discrepancies.add(discrepancy);
                }
            });

            List<Purchase> getFulfilledPurchaseOrders = purchaseRepository.findFulfilledPurchaseOrders(fromDateTime, toDateTime, tenantName);

            // Create a specification for the query
            Specification<Purchase> spec = Specification.where(null);

            // Add tenant name filtering
            spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("tenant"), tenantName));

            // Add date range filtering
            spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.between(root.get("created_at"), fromDateTime, toDateTime));

            List<Purchase> totalOrders = purchaseRepository.findAll(spec);

            totalPurchaseOrderCompliance = (totalOrders.size() > 0) ? ((double) getFulfilledPurchaseOrders.size() / totalOrders.size()) * 100 : 0.0;

            // Fetch prescriptions with items
            List<Prescriptions> prescriptions = prescriptionsRepository.findPrescriptionsWithItems(fromDateTime, toDateTime, tenantName);

            // Extract product IDs from prescription items
            List<String> prescriptionProductIds = prescriptions.stream()
                    .flatMap(p -> p.getPrescription_items().stream())
                    .map(PrescriptionItem::getProduct_id)
                    .distinct()
                    .collect(Collectors.toList());

            // Fetch sales items that match the prescription product IDs
            List<SalesItem> matchingSalesItems = salesItemRepository.findSalesItemsByProductIdsAndDateRange(fromDateTime, toDateTime, tenantName, prescriptionProductIds);

            // Calculate compliance
            int totalPrescriptions = prescriptions.size();
            int fulfilledPrescriptions = (int) prescriptions.stream()
                    .filter(p -> p.getPrescription_items().stream()
                            .allMatch(pi -> matchingSalesItems.stream()
                                    .anyMatch(si -> si.getProduct_id().equals(pi.getProduct_id()) &&
                                            si.getQuantity() >= pi.getQuantity())))
                    .count();

            // Compliance percentage
            totalPrescriptionCompliance = (totalPrescriptions > 0) ? ((double) fulfilledPrescriptions / totalPrescriptions) * 100 : 0.0;

            // Create the response map
            Map<String, Object> auditComplianceData = new LinkedHashMap<>();
            auditComplianceData.put("total_inventory_discrepancies", discrepancies.stream().count());
            auditComplianceData.put("fulfilled_purchase_orders", getFulfilledPurchaseOrders.stream().count());
            auditComplianceData.put("total_purchase_order_compliance", totalPurchaseOrderCompliance);
            auditComplianceData.put("total_prescription_compliance", totalPrescriptionCompliance);

            // Create and return the structured ApiResponseStructure
            return new ApiResponseStructure<>("Success", 200, "Data retrieved.", auditComplianceData);
        }
        createLogger.createLogger("error", path, "GET", "Please provide start-date and end-date with valid dates.", "");
        return new ApiResponseStructure<>("Failure", 500, "Please provide start-date and end-date with valid dates.", Collections.emptyMap());
    }

    public ApiResponseStructure <Map<String, Object>> getAuditComplianceReport(SalesRequest salesRequest, String reportType){
        // Extract Authorization headers from the request
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.AUTHORIZATION, httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION));

        String tenantName = jwtRequestUtils.getTenantNameFromHeaders(headers);

        // Validate presence of fromDate and toDate
        if (salesRequest.getStartDate() == null || salesRequest.getEndDate() == null) {
            createLogger.createLogger("error", path, "POST", "Both 'startDate' and 'endDate' must be provided.", "");
            return new ApiResponseStructure<>("Error", HttpStatus.BAD_REQUEST.value(), "Both 'startDate' and 'endDate' must be provided.", new HashMap<>());
        }

        // Create a specification for the query
        Specification<SalesItem> spec = Specification.where(null);

        // Add tenant name filtering
        spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("tenant"), tenantName));

        // Check the data types for getStartDate() and getEndDate()
        LocalDate fromDate = salesRequest.getStartDate(); // Ensure this is LocalDate
        LocalDate toDate = salesRequest.getEndDate();     // Ensure this is LocalDate

        // Convert LocalDate to LocalDateTime
        LocalDateTime fromDateTime = fromDate.atStartOfDay();
        LocalDateTime toDateTime = toDate.atTime(23, 59, 59);

        // Fetch the data with pagination
        Pageable pageable = PageRequest.of(salesRequest.getPage() != null ? salesRequest.getPage() : 0, // Page number is zero-based
                salesRequest.getSize() != null ? salesRequest.getSize() : 20);

        // Switch cases to handle various report type
        switch (reportType) {
            case "inventoryAudit":
                List<Map<String, Object>> discrepancies = new ArrayList<>();
                Page<Object[]> inventoryList = inventoryRepository.findInventoriesByDateRange(pageable, fromDateTime, toDateTime, tenantName);

                inventoryList.forEach(inventory -> {
                    Long productId = Long.valueOf(inventory[0].toString());
                    Integer actualStock = Integer.parseInt(inventory[1].toString());

                    // Calculate expected stock from purchases and sales
                    List<GRNItem> purchasedStockItems = grnItemRepository.getTotalPurchasedQuantity(fromDateTime, toDateTime, tenantName, productId);
                    List<SalesItem> soldStockItems = salesItemRepository.findSalesItemsByDateRangeAndProductId(fromDateTime, toDateTime, tenantName, productId);

                    // Calculate total purchased and sold quantities
                    Integer purchasedStock = purchasedStockItems.stream().mapToInt(GRNItem::getQuantity).sum();
                    Integer soldStock = soldStockItems.stream().mapToInt(SalesItem::getQuantity).sum();
                    Integer expectedStock = purchasedStock - soldStock;

                    if (!actualStock.equals(expectedStock)) {
                        Map<String, Object> discrepancy = new LinkedHashMap<>();
                        discrepancy.put("product_id", productId);
                        discrepancy.put("product_name", productRepository.findProductNameById(productId, tenantName));
                        Long categoryId = productRepository.findCategoryIdByProductId(productId, tenantName);
                        Long brandId = productRepository.findBrandIdByProductId(productId, tenantName);
                        discrepancy.put("category_id", categoryId);
                        discrepancy.put("category_name", categoryRepository.findCategoryNameById(categoryId, tenantName));
                        discrepancy.put("brand_id", brandId);
                        discrepancy.put("brand_name", brandRepository.findBrandNameById(brandId, tenantName));
                        discrepancy.put("inventory_quantity", actualStock);
                        discrepancy.put("received_quantity", expectedStock);
                        discrepancy.put("discrepancy", actualStock - expectedStock);
                        discrepancies.add(discrepancy);
                    }
                });

                // Prepare column details
                Map<String, Object> column = new LinkedHashMap<>();
                column.put("product_id", "PRODUCT ID");
                column.put("product_name", "PRODUCT NAME");
                column.put("category_id", "CATEGORY ID");
                column.put("category_name", "CATEGORY NAME");
                column.put("brand_id", "BRAND ID");
                column.put("brand_name", "BRAND NAME");
                column.put("inventory_quantity", "INVENTORY QUANTITY");
                column.put("received_quantity", "RECEIVED QUANTITY");
                column.put("discrepancy", "DISCREPANCY");

                // Prepare pagination details
                Map<String, Object> pagination = new LinkedHashMap<>();
                pagination.put("currentPage", inventoryList.getNumber() + 1); // Page number is 0-based
                pagination.put("totalPages", inventoryList.getTotalPages());
                pagination.put("pageSize", pageable.getPageSize());
                pagination.put("totalRecords", inventoryList.stream().count());

                // Prepare response data
                Map<String, Object> salesData = new LinkedHashMap<>();
                salesData.put("module", "audit-compliance");
                salesData.put("reportType", reportType);
                salesData.put("startDate", String.valueOf(fromDate));
                salesData.put("endDate", String.valueOf(toDate));
                salesData.put("reportData", discrepancies);
                salesData.put("column", column);
                salesData.put("pagination", pagination);

                // Create and return the structured ApiResponseStructure
                return new ApiResponseStructure<>("Success", 200, "Data retrieved.", salesData);
            case "purchaseOrderCompliance":
                Page<Object[]> complianceResults = purchaseRepository.findPurchaseOrderCompliance(pageable, fromDateTime, toDateTime, tenantName);
                List<Map<String, Object>> purchaseOrderCompliance = new ArrayList<>();

                purchaseOrderCompliance = complianceResults.stream().map(result -> {
                    Map<String, Object> complianceData = new LinkedHashMap<>();
                    complianceData.put("product_id", result[0]);
                    Long productId = Long.valueOf(result[0].toString());
                    complianceData.put("product_name", productRepository.findProductNameById(productId, tenantName));
                    Long categoryId = productRepository.findCategoryIdByProductId(productId, tenantName);
                    Long brandId = productRepository.findBrandIdByProductId(productId, tenantName);
                    complianceData.put("category_id", categoryId);
                    complianceData.put("category_name", categoryRepository.findCategoryNameById(categoryId, tenantName));
                    complianceData.put("brand_id", brandId);
                    complianceData.put("brand_name", brandRepository.findBrandNameById(brandId, tenantName));
                    complianceData.put("ordered_quantity", result[1]);
                    complianceData.put("received_quantity", result[2]);
                    complianceData.put("compliance_status", result[3]);

                    // Calculate compliance percentage
                    Integer orderedQuantity = Integer.parseInt(result[1].toString());
                    Integer receivedQuantity = Integer.parseInt(result[2].toString());
                    double compliancePercentage = (orderedQuantity > 0) ? ((double) receivedQuantity / orderedQuantity) * 100 : 0;

                    complianceData.put("compliance_percentage", compliancePercentage);

                    return complianceData;
                }).collect(Collectors.toList());

                // Prepare column details
                Map<String, Object> columns = new LinkedHashMap<>();
                columns.put("product_id", "PRODUCT ID");
                columns.put("product_name", "PRODUCT NAME");
                columns.put("category_id", "CATEGORY ID");
                columns.put("category_name", "CATEGORY NAME");
                columns.put("brand_id", "BRAND ID");
                columns.put("brand_name", "BRAND NAME");
                columns.put("ordered_quantity", "ORDERED QUANTITY");
                columns.put("received_quantity", "RECEIVED QUANTITY");
                columns.put("compliance_status", "COMPLIANCE STATUS");
                columns.put("compliance_percentage", "COMPLIANCE PERCENTAGE");

                // Prepare pagination details
                Map<String, Object> paginations = new LinkedHashMap<>();
                paginations.put("currentPage", complianceResults.getNumber() + 1); // Page number is 0-based
                paginations.put("totalPages", complianceResults.getTotalPages());
                paginations.put("pageSize", pageable.getPageSize());
                paginations.put("totalRecords", complianceResults.stream().count());

                // Prepare response data
                Map<String, Object> purchaseData = new LinkedHashMap<>();
                purchaseData.put("module", "audit-compliance");
                purchaseData.put("reportType", reportType);
                purchaseData.put("startDate", String.valueOf(fromDate));
                purchaseData.put("endDate", String.valueOf(toDate));
                purchaseData.put("reportData", purchaseOrderCompliance);
                purchaseData.put("column", columns);
                purchaseData.put("pagination", paginations);

                // Create and return the structured ApiResponseStructure
                return new ApiResponseStructure<>("Success", 200, "Data retrieved.", purchaseData);
            case "prescriptionCompliance":
                Page<Object[]> prescriptionComplianceResults = prescriptionsRepository.findCompliantPrescriptions(pageable, fromDateTime, toDateTime, tenantName);
                List<Map<String, Object>> prescriptionCompliance = new ArrayList<>();

                prescriptionCompliance = prescriptionComplianceResults.stream().map(result -> {
                    Map<String, Object> complianceData = new LinkedHashMap<>();
                    if("product".equals(result[4])) {
                        Long productId = Long.valueOf(result[0].toString());
                        complianceData.put("product_id", productId);
                        complianceData.put("product_name", productRepository.findProductNameById(productId, tenantName));
                        Long categoryId = productRepository.findCategoryIdByProductId(productId, tenantName);
                        Long brandId = productRepository.findBrandIdByProductId(productId, tenantName);
                        complianceData.put("category_id", categoryId);
                        complianceData.put("category_name", categoryRepository.findCategoryNameById(categoryId, tenantName));
                        complianceData.put("brand_id", brandId);
                        complianceData.put("brand_name", brandRepository.findBrandNameById(brandId, tenantName));
                    }
                    complianceData.put("prescribed_quantity", Integer.parseInt(result[1].toString()));
                    complianceData.put("sale_quantity", Integer.parseInt(result[2].toString()));
                    complianceData.put("compliance_status", result[3].toString());

                    // Calculate compliance percentage
                    Integer prescripedQuantity = Integer.parseInt(result[1].toString());
                    Integer saledQuantity = Integer.parseInt(result[2].toString());
                    double compliancePercentage = (prescripedQuantity > 0) ? ((double) saledQuantity / prescripedQuantity) * 100 : 0;

                    complianceData.put("compliance_percentage", compliancePercentage);

                    return complianceData;
                }).collect(Collectors.toList());

                // Prepare column details
                Map<String, Object> columnss = new LinkedHashMap<>();
                columnss.put("product_id", "PRODUCT ID");
                columnss.put("product_name", "PRODUCT NAME");
                columnss.put("category_id", "CATEGORY ID");
                columnss.put("category_name", "CATEGORY NAME");
                columnss.put("brand_id", "BRAND ID");
                columnss.put("brand_name", "BRAND NAME");
                columnss.put("prescribed_quantity", "PRESCRIBED QUANTITY");
                columnss.put("sale_quantity", "SALE QUANTITY");
                columnss.put("compliance_status", "COMPLIANCE STATUS");
                columnss.put("compliance_percentage", "COMPLIANCE PERCENTAGE");

                // Prepare pagination details
                Map<String, Object> paginationss = new LinkedHashMap<>();
                paginationss.put("currentPage", prescriptionComplianceResults.getNumber() + 1); // Page number is 0-based
                paginationss.put("totalPages", prescriptionComplianceResults.getTotalPages());
                paginationss.put("pageSize", pageable.getPageSize());
                paginationss.put("totalRecords", prescriptionComplianceResults.stream().count());

                // Prepare response data
                Map<String, Object> prescriptionData = new LinkedHashMap<>();
                prescriptionData.put("module", "audit-compliance");
                prescriptionData.put("reportType", reportType);
                prescriptionData.put("startDate", String.valueOf(fromDate));
                prescriptionData.put("endDate", String.valueOf(toDate));
                prescriptionData.put("reportData", prescriptionCompliance);
                prescriptionData.put("column", columnss);
                prescriptionData.put("pagination", paginationss);

                // Create and return the structured ApiResponseStructure
                return new ApiResponseStructure<>("Success", 200, "Data retrieved.", prescriptionData);
            default:
                createLogger.createLogger("error", path, "POST", "Invalid report type provided.", "runtime");
                return new ApiResponseStructure<>("Error", 400, "Invalid report type provided.", new HashMap<>());
        }
    }
}