/*
 * File: src/main/java/reports/service/RevenueService.java
 * Description: This service class provides functionality for handling revenue-related operations in the ERP reporting system.
 * It includes methods for retrieving KPI data and generating revenue reports based on sales and purchase data.
 */

package com.nebula.erp.reports.service;

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.StockRepository;
import com.nebula.erp.reports.repository.product.BrandRepository;
import com.nebula.erp.reports.repository.product.CategoryRepository;
import com.nebula.erp.reports.repository.product.TaxRepository;
import com.nebula.erp.reports.repository.purchase.*;
import com.nebula.erp.reports.repository.sales.PrescriptionItemRepository;
import com.nebula.erp.reports.repository.sales.SalesRepository;
import com.nebula.erp.reports.repository.sales.SalesReturnItemRepository;
import com.nebula.erp.reports.repository.sales.SalesItemRepository;
import com.nebula.erp.reports.repository.product.ProductRepository;
import com.nebula.erp.reports.requestmodel.SalesRequest;
import com.nebula.erp.reports.utility.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

@Service
public class RevenueService {

    @Autowired
    private PurchaseRepository purchaseRepository;

    @Autowired
    private SalesRepository salesRepository;

    @Autowired
    private SalesItemRepository salesItemRepository;

    @Autowired
    private SalesReturnItemRepository salesReturnItemRepository;

    @Autowired
    private SupplierRepository supplierRepository;

    @Autowired
    private GRNRepository grnRepository;

    @Autowired
    private PurchaseReturnRepository purchaseReturnRepository;

    @Autowired
    private PurchaseReturnItemRepository purchaseReturnItemRepository;

    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private GRNItemRepository grnItemRepository;

    @Autowired
    private PurchaseItemRepository purchaseItemRepository;

    @Autowired
    private CategoryRepository categoryRepository;

    @Autowired
    private TaxRepository taxRepository;

    @Autowired
    private BrandRepository brandRepository;

    @Autowired
    private StockRepository stockRepository;

    @Autowired
    private PrescriptionItemRepository prescriptionItemRepository;

    @Autowired
    private JwtRequestUtils jwtRequestUtils;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private JwtUtils jwtUtils;

    @Value("${ehr.host.api}")
    private String ehrHostUrl;

    @Autowired
    private CreateLogger createLogger;

    private static final String path = "/reports/revenue";

    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));

        // Extract tenant name from the request headers
        String tenantName = jwtRequestUtils.getTenantNameFromHeaders(headers);

        // Check for valid date range
        if (fromDate == null || toDate == null) {
            createLogger.createLogger("error", path, "GET", "Please provide valid start-date and end-date.", "");
            return new ApiResponseStructure<>("Failure", 400, "Please provide valid start-date and end-date.",
                    Collections.emptyMap());
        }

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

        // Fetch sales items for the given date range and tenant name
        List<SalesItem> salesItems = salesItemRepository.findAllSalesItemsByDateRange(fromDateTime, toDateTime,
                tenantName);

        if (salesItems.isEmpty()) {
            return new ApiResponseStructure<>("Success", 200, "No data found for the given date range.",
                    Collections.emptyMap());
        }

        // Initialize totals
        BigDecimal totalTaxesCollected = BigDecimal.ZERO;
        BigDecimal gstCollected = BigDecimal.ZERO;
        BigDecimal totalSalesAmount = BigDecimal.ZERO;

        for (SalesItem item : salesItems) {
            BigDecimal itemTotalPrice = BigDecimal.valueOf(item.getTotal_price());
            totalSalesAmount = totalSalesAmount.add(itemTotalPrice);

            if (item.getTax_id() != null) {
                // Fetch tax rate and tax type
                BigDecimal taxRate = taxRepository.findTaxRateById(item.getTax_id(), tenantName);
                String taxType = taxRepository.findTaxTypeById(item.getTax_id(), tenantName);

                if (taxRate != null) {
                    // Calculate tax amount
                    BigDecimal taxAmount = itemTotalPrice.multiply(taxRate).divide(BigDecimal.valueOf(100),
                            RoundingMode.HALF_UP);
                    totalTaxesCollected = totalTaxesCollected.add(taxAmount);

                    if ("GST".equalsIgnoreCase(taxType)) {
                        gstCollected = gstCollected.add(taxAmount);
                    }
                }
            }
        }

        // Calculate additional metrics
        long totalItemsSold = salesItems.size();
        BigDecimal averageSalesAmount = totalItemsSold > 0
                ? totalSalesAmount.divide(BigDecimal.valueOf(totalItemsSold), RoundingMode.HALF_UP)
                : BigDecimal.ZERO;

        // Prepare response data
        Map<String, Object> salesData = new LinkedHashMap<>();
        salesData.put("total_taxes_collected", MoneyUtils.truncateToTwoDecimals(totalTaxesCollected.doubleValue()));
        salesData.put("gst_collected", MoneyUtils.truncateToTwoDecimals(gstCollected.doubleValue()));
        salesData.put("total_sales_with_tax",
                MoneyUtils.truncateToTwoDecimals(totalSalesAmount.add(totalTaxesCollected).doubleValue())); // Total
                                                                                                            // sales
                                                                                                            // including
                                                                                                            // taxes
        salesData.put("total_sales_amount", MoneyUtils.truncateToTwoDecimals(totalSalesAmount.doubleValue()));
        salesData.put("total_items_sold", totalItemsSold);
        salesData.put("average_sales_amount_per_item",
                MoneyUtils.truncateToTwoDecimals(averageSalesAmount.doubleValue()));

        // Create and return the structured ApiResponseStructure
        return new ApiResponseStructure<>("Success", 200, "Data retrieved successfully.", salesData);
    }

    public ApiResponseStructure<Map<String, Object>> getRevenueReport(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<>());
        }

        // Convert LocalDate to LocalDateTime
        LocalDateTime fromDateTime = salesRequest.getStartDate().atStartOfDay();
        LocalDateTime toDateTime = salesRequest.getEndDate().atTime(23, 59, 59);

        // 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));

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

        // Switch cases to handle various report type
        switch (reportType) {
            case "brandBasedReport":
                // Add dynamic conditions
                if (salesRequest.getConditions() != null) {
                    for (SalesRequest.Condition condition : salesRequest.getConditions()) {
                        switch (condition.getOperator()) {
                            case "equals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder
                                        .equal(root.get(condition.getField()), condition.getValue()));
                                break;
                            case "greaterThan":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "lessThan":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder
                                        .lessThan(root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "greaterThanOrEquals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "lessThanOrEquals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            default:
                                createLogger.createLogger("error", path, "POST",
                                        "Operator " + condition.getOperator() + " is not supported.", "");
                                throw new IllegalArgumentException(
                                        "Operator " + condition.getOperator() + " is not supported.");
                        }
                    }
                }

                // Fetch brand-based data
                List<Object[]> brandData = salesItemRepository.findProductSalesData(fromDateTime, toDateTime,
                        tenantName, "product");

                // Map to store aggregated brand metrics (sales, purchases, and stocks)
                Map<String, Map<String, Integer>> aggregatedBrandMetrics = new LinkedHashMap<>();

                for (Object[] row : brandData) {
                    Long productId = Long.valueOf(row[0].toString()); // Product ID
                    Integer totalSales = row[1] != null ? ((Number) row[1]).intValue() : 0; // Total Sales
                    Integer totalPurchases = grnItemRepository.findTotalPurchasesByProductId(productId, tenantName,
                            fromDateTime, toDateTime); // Fetch total purchases
                    Integer totalStocks = stockRepository.findTotalStockByProductId(productId, tenantName, fromDateTime,
                            toDateTime); // Fetch total stock

                    // Fetch brand ID and brand name
                    Long brandId = productRepository.findBrandIdByProductId(productId, tenantName); // Fetch brand ID
                    String brandName = brandRepository.findBrandNameById(brandId, tenantName); // Fetch brand name

                    // Initialize or update brand data
                    aggregatedBrandMetrics.putIfAbsent(brandName, new LinkedHashMap<>());
                    Map<String, Integer> brandMetrics = aggregatedBrandMetrics.get(brandName);

                    // Update sales, purchases, and stocks for the brand
                    brandMetrics.put("totalSales", brandMetrics.getOrDefault("totalSales", 0) + totalSales);
                    brandMetrics.put("totalPurchases", brandMetrics.getOrDefault("totalPurchases", 0)
                            + (totalPurchases != null ? totalPurchases : 0));
                    brandMetrics.put("totalStock",
                            brandMetrics.getOrDefault("totalStock", 0) + (totalStocks != null ? totalStocks : 0));
                }

                // Prepare report data
                List<Map<String, Object>> reportData = new ArrayList<>();
                for (Map.Entry<String, Map<String, Integer>> entry : aggregatedBrandMetrics.entrySet()) {
                    Map<String, Object> data = new LinkedHashMap<>();
                    data.put("brandName", entry.getKey()); // Brand Name
                    data.put("totalSales", entry.getValue().get("totalSales")); // Total Sales
                    data.put("totalPurchases", entry.getValue().get("totalPurchases")); // Total Purchases
                    data.put("totalStock", entry.getValue().get("totalStock")); // Total Stock
                    reportData.add(data);
                }

                // Prepare summary details (if needed)
                Map<String, Object> summary = new LinkedHashMap<>();
                summary.put("totalBrands", reportData.size()); // Total unique brands

                // Prepare column details
                Map<String, Object> column = new LinkedHashMap<>();
                column.put("brandName", "BRAND NAME");
                column.put("totalPurchases", "TOTAL PURCHASES");
                column.put("totalSales", "TOTAL SALES");
                column.put("totalStock", "TOTAL STOCK");

                // Calculate pagination details
                int currentPage = salesRequest.getPage(); // Assuming `getPage()` provides 1-based page number
                int pageSize = salesRequest.getSize(); // Assuming `getSize()` provides the size per page
                int totalRecords = reportData.size(); // Total records available
                int totalPages = (int) Math.ceil((double) totalRecords / pageSize); // Calculate total pages

                Map<String, Object> pagination = new LinkedHashMap<>();
                pagination.put("currentPage", currentPage);
                pagination.put("totalPages", totalPages);
                pagination.put("pageSize", pageSize);
                pagination.put("totalRecords", totalRecords);

                // Prepare final response data
                Map<String, Object> responseData = new LinkedHashMap<>();
                responseData.put("module", "revenue");
                responseData.put("reportType", reportType);
                responseData.put("startDate", String.valueOf(salesRequest.getStartDate()));
                responseData.put("endDate", String.valueOf(salesRequest.getEndDate()));
                responseData.put("reportData", reportData);
                responseData.put("column", column);
                responseData.put("pagination", pagination);
                responseData.put("summary", summary);

                return new ApiResponseStructure<>("Success", 200, "Data retrieved.", responseData);
            case "categoryBasedReport":
                // Add dynamic conditions
                if (salesRequest.getConditions() != null) {
                    for (SalesRequest.Condition condition : salesRequest.getConditions()) {
                        switch (condition.getOperator()) {
                            case "equals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder
                                        .equal(root.get(condition.getField()), condition.getValue()));
                                break;
                            case "greaterThan":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "lessThan":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder
                                        .lessThan(root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "greaterThanOrEquals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "lessThanOrEquals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            default:
                                createLogger.createLogger("error", path, "POST",
                                        "Operator " + condition.getOperator() + " is not supported.", "");
                                throw new IllegalArgumentException(
                                        "Operator " + condition.getOperator() + " is not supported.");
                        }
                    }
                }

                // Fetch category-based data
                List<Object[]> categoryData = salesItemRepository.findProductSalesData(fromDateTime, toDateTime,
                        tenantName, "product");

                // Map to store aggregated category metrics (sales, purchases, and stocks)
                Map<String, Map<String, Integer>> aggregatedCategoryMetrics = new LinkedHashMap<>();

                for (Object[] row : categoryData) {
                    Long productId = Long.valueOf(row[0].toString()); // Product ID
                    Integer totalSales = row[1] != null ? ((Number) row[1]).intValue() : 0; // Total Sales
                    Integer totalPurchases = grnItemRepository.findTotalPurchasesByProductId(productId, tenantName,
                            fromDateTime, toDateTime); // Fetch total purchases
                    Integer totalStocks = stockRepository.findTotalStockByProductId(productId, tenantName, fromDateTime,
                            toDateTime); // Fetch total stock

                    Long categoryId = productRepository.findCategoryIdByProductId(productId, tenantName); // Fetch
                                                                                                          // category ID
                    String categoryName = categoryRepository.findCategoryNameById(categoryId, tenantName); // Fetch
                                                                                                           // category
                                                                                                           // name

                    // Initialize or update brand data
                    aggregatedCategoryMetrics.putIfAbsent(categoryName, new LinkedHashMap<>());
                    Map<String, Integer> brandMetrics = aggregatedCategoryMetrics.get(categoryName);

                    // Update sales, purchases, and stocks for the brand
                    brandMetrics.put("totalSales", brandMetrics.getOrDefault("totalSales", 0) + totalSales);
                    brandMetrics.put("totalPurchases", brandMetrics.getOrDefault("totalPurchases", 0)
                            + (totalPurchases != null ? totalPurchases : 0));
                    brandMetrics.put("totalStock",
                            brandMetrics.getOrDefault("totalStock", 0) + (totalStocks != null ? totalStocks : 0));
                }

                // Prepare report data
                reportData = new ArrayList<>();
                for (Map.Entry<String, Map<String, Integer>> entry : aggregatedCategoryMetrics.entrySet()) {
                    Map<String, Object> data = new LinkedHashMap<>();
                    data.put("categoryName", entry.getKey()); // Category Name
                    data.put("totalSales", entry.getValue().get("totalSales")); // Total Sales
                    data.put("totalPurchases", entry.getValue().get("totalPurchases")); // Total Purchases
                    data.put("totalStock", entry.getValue().get("totalStock")); // Total Stock
                    reportData.add(data);
                }

                // Prepare summary details (if needed)
                summary = new LinkedHashMap<>();
                summary.put("totalCategories", reportData.size()); // Total unique categories

                // Prepare column details
                column = new LinkedHashMap<>();
                column.put("categoryName", "CATEGORY NAME");
                column.put("totalPurchases", "TOTAL PURCHASES");
                column.put("totalSales", "TOTAL SALES");
                column.put("totalStock", "TOTAL STOCK");

                // Calculate pagination details
                currentPage = salesRequest.getPage(); // Assuming `getPage()` provides 1-based page number
                pageSize = salesRequest.getSize(); // Assuming `getSize()` provides the size per page
                totalRecords = reportData.size(); // Total records available
                totalPages = (int) Math.ceil((double) totalRecords / pageSize); // Calculate total pages

                pagination = new LinkedHashMap<>();
                pagination.put("currentPage", currentPage);
                pagination.put("totalPages", totalPages);
                pagination.put("pageSize", pageSize);
                pagination.put("totalRecords", totalRecords);

                // Prepare final response data
                responseData = new LinkedHashMap<>();
                responseData.put("module", "revenue");
                responseData.put("reportType", reportType);
                responseData.put("startDate", String.valueOf(salesRequest.getStartDate()));
                responseData.put("endDate", String.valueOf(salesRequest.getEndDate()));
                responseData.put("reportData", reportData);
                responseData.put("column", column);
                responseData.put("pagination", pagination);
                responseData.put("summary", summary);

                return new ApiResponseStructure<>("Success", 200, "Data retrieved.", responseData);
            case "productBasedReport":
                // Add dynamic conditions
                if (salesRequest.getConditions() != null) {
                    for (SalesRequest.Condition condition : salesRequest.getConditions()) {
                        switch (condition.getOperator()) {
                            case "equals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder
                                        .equal(root.get(condition.getField()), condition.getValue()));
                                break;
                            case "greaterThan":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "lessThan":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder
                                        .lessThan(root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "greaterThanOrEquals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            case "lessThanOrEquals":
                                spec = spec.and((root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(
                                        root.get(condition.getField()), (Comparable) condition.getValue()));
                                break;
                            default:
                                createLogger.createLogger("error", path, "POST",
                                        "Operator " + condition.getOperator() + " is not supported.", "");
                                throw new IllegalArgumentException(
                                        "Operator " + condition.getOperator() + " is not supported.");
                        }
                    }
                }

                // Fetch product-based data
                List<Object[]> productData = salesItemRepository.findProductSalesData(fromDateTime, toDateTime,
                        tenantName, "product");

                // Map to store aggregated product metrics (sales, purchases, and stocks)
                Map<String, Map<String, Integer>> aggregatedProductMetrics = new LinkedHashMap<>();

                for (Object[] row : productData) {
                    Long productId = Long.valueOf(row[0].toString()); // Product ID
                    Integer totalSales = row[1] != null ? ((Number) row[1]).intValue() : 0; // Total Sales
                    Integer totalPurchases = grnItemRepository.findTotalPurchasesByProductId(productId, tenantName,
                            fromDateTime, toDateTime); // Fetch total purchases
                    Integer totalStocks = stockRepository.findTotalStockByProductId(productId, tenantName, fromDateTime,
                            toDateTime); // Fetch total stock

                    String productName = productRepository.findProductNameById(productId, tenantName); // Fetch product
                                                                                                       // name

                    // Initialize or update brand data
                    aggregatedProductMetrics.putIfAbsent(productName, new LinkedHashMap<>());
                    Map<String, Integer> brandMetrics = aggregatedProductMetrics.get(productName);

                    // Update sales, purchases, and stocks for the brand
                    brandMetrics.put("totalSales", brandMetrics.getOrDefault("totalSales", 0) + totalSales);
                    brandMetrics.put("totalPurchases", brandMetrics.getOrDefault("totalPurchases", 0)
                            + (totalPurchases != null ? totalPurchases : 0));
                    brandMetrics.put("totalStock",
                            brandMetrics.getOrDefault("totalStock", 0) + (totalStocks != null ? totalStocks : 0));
                }

                // Prepare report data
                reportData = new ArrayList<>();
                for (Map.Entry<String, Map<String, Integer>> entry : aggregatedProductMetrics.entrySet()) {
                    Map<String, Object> data = new LinkedHashMap<>();
                    data.put("productName", entry.getKey()); // Product Name
                    data.put("totalSales", entry.getValue().get("totalSales")); // Total Sales
                    data.put("totalPurchases", entry.getValue().get("totalPurchases")); // Total Purchases
                    data.put("totalStock", entry.getValue().get("totalStock")); // Total Stock
                    reportData.add(data);
                }

                // Prepare summary details (if needed)
                summary = new LinkedHashMap<>();
                summary.put("totalProducts", reportData.size()); // Total unique products

                // Prepare column details
                column = new LinkedHashMap<>();
                column.put("productName", "PRODUCT NAME");
                column.put("totalPurchases", "TOTAL PURCHASES");
                column.put("totalSales", "TOTAL SALES");
                column.put("totalStock", "TOTAL STOCK");

                // Calculate pagination details
                currentPage = salesRequest.getPage(); // Assuming `getPage()` provides 1-based page number
                pageSize = salesRequest.getSize(); // Assuming `getSize()` provides the size per page
                totalRecords = reportData.size(); // Total records available
                totalPages = (int) Math.ceil((double) totalRecords / pageSize); // Calculate total pages

                pagination = new LinkedHashMap<>();
                pagination.put("currentPage", currentPage);
                pagination.put("totalPages", totalPages);
                pagination.put("pageSize", pageSize);
                pagination.put("totalRecords", totalRecords);

                // Prepare final response data
                responseData = new LinkedHashMap<>();
                responseData.put("module", "revenue");
                responseData.put("reportType", reportType);
                responseData.put("startDate", String.valueOf(salesRequest.getStartDate()));
                responseData.put("endDate", String.valueOf(salesRequest.getEndDate()));
                responseData.put("reportData", reportData);
                responseData.put("column", column);
                responseData.put("pagination", pagination);
                responseData.put("summary", summary);

                return new ApiResponseStructure<>("Success", 200, "Data retrieved.", responseData);
            case "doctorBasedReport":
                // Fetch prescription data
                List<PrescriptionItem> prescriptionItems = prescriptionItemRepository
                        .findItemsByTenantAndDateRange(fromDateTime, toDateTime, tenantName);

                // Map to store doctor-based metrics
                Map<String, Map<String, Integer>> aggregatedDoctorMetrics = new LinkedHashMap<>();

                // Collect unique doctor IDs
                Set<String> doctorIds = new HashSet<>();
                for (PrescriptionItem item : prescriptionItems) {
                    if (item.getPrescription() != null) {
                        doctorIds.add(item.getPrescription().getDoctor_id());
                    }
                }

                // Fetch doctor names only once per doctor
                Map<String, String> doctorMap = new HashMap<>();
                for (String doctorId : doctorIds) {
                    doctorMap.put(doctorId, fetchDoctorName(tenantName, doctorId, headers));
                }

                for (PrescriptionItem item : prescriptionItems) {
                    // Fetch doctor details
                    Prescriptions prescription = item.getPrescription();
                    String doctorId = prescription.getDoctor_id();
                    String doctorName = doctorMap.getOrDefault(doctorId, "Unknown Doctor"); // Fetch doctor name via API

                    // Determine type
                    String type = "Medication";

                    // Initialize or update doctor data
                    aggregatedDoctorMetrics.putIfAbsent(doctorName, new LinkedHashMap<>());
                    Map<String, Integer> doctorMetrics = aggregatedDoctorMetrics.get(doctorName);

                    // Update metrics for the doctor
                    doctorMetrics.put(type, doctorMetrics.getOrDefault(type, 0) + item.getQuantity());
                }

                // Prepare report data
                reportData = new ArrayList<>();
                for (Map.Entry<String, Map<String, Integer>> entry : aggregatedDoctorMetrics.entrySet()) {
                    String doctorName = entry.getKey();
                    Map<String, Integer> metrics = entry.getValue();

                    for (Map.Entry<String, Integer> metric : metrics.entrySet()) {
                        Map<String, Object> data = new LinkedHashMap<>();
                        data.put("doctorName", doctorName); // Doctor Name
                        data.put("type", metric.getKey()); // Type (Medication/Service)
                        data.put("totalSales", metric.getValue()); // Total Sales
                        reportData.add(data);
                    }
                }

                // Prepare column details
                column = new LinkedHashMap<>();
                column.put("doctorName", "DOCTOR NAME");
                column.put("type", "TYPE");
                column.put("totalSales", "TOTAL SALES");

                // Calculate pagination details
                currentPage = salesRequest.getPage(); // Assuming `getPage()` provides 1-based page number
                pageSize = salesRequest.getSize(); // Assuming `getSize()` provides the size per page
                totalRecords = reportData.size(); // Total records available
                totalPages = (int) Math.ceil((double) totalRecords / pageSize); // Calculate total pages

                pagination = new LinkedHashMap<>();
                pagination.put("currentPage", currentPage);
                pagination.put("totalPages", totalPages);
                pagination.put("pageSize", pageSize);
                pagination.put("totalRecords", totalRecords);

                // Prepare final response data
                responseData = new LinkedHashMap<>();
                responseData.put("module", "revenue");
                responseData.put("reportType", reportType);
                responseData.put("startDate", String.valueOf(salesRequest.getStartDate()));
                responseData.put("endDate", String.valueOf(salesRequest.getEndDate()));
                responseData.put("reportData", reportData);
                responseData.put("column", column);
                responseData.put("pagination", pagination);

                return new ApiResponseStructure<>("Success", 200, "Data retrieved.", responseData);
            default:
                createLogger.createLogger("error", path, "POST", "", "");
                return new ApiResponseStructure<>("Error", 400, "Invalid report type provided.", new HashMap<>());
        }
    }

    public String fetchDoctorName(String tenantName, String doctorId, HttpHeaders headers) {
        String apiUrl = ehrHostUrl + tenantName + "/Practitioner/" + doctorId;

        RestTemplate restTemplate = new RestTemplate();

        // Extract the JWT token from the Authorization header
        String token = jwtUtils.extractTokenFromHeaders(headers);

        headers.set("Authorization", "Bearer " + token);
        headers.set("Accept", "application/json");

        // Create the request entity
        HttpEntity<String> entity = new HttpEntity<>(headers);

        try {
            // Make the API call
            ResponseEntity<Map> response = restTemplate.exchange(apiUrl, HttpMethod.GET, entity, Map.class);

            // Parse the response to get the doctor's name
            Map<String, Object> responseBody = response.getBody();
            if (responseBody != null && responseBody.containsKey("name")) {
                List<Map<String, Object>> names = (List<Map<String, Object>>) responseBody.get("name");
                if (names != null && !names.isEmpty()) {
                    Map<String, Object> name = names.get(0);
                    List<String> givenNames = (List<String>) name.get("given");
                    String familyName = (String) name.get("family");

                    if (givenNames != null && !givenNames.isEmpty()) {
                        return String.join(" ", givenNames) + " " + familyName;
                    }
                }
            }
        } catch (Exception e) {
            createLogger.createLogger("error", path, "POST", "", "");
        }
        return "Unknown Doctor";
    }

}