package com.nebula.erp.sales.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nebula.erp.sales.model.PaymentHistory;
import com.nebula.erp.sales.model.Payments;
import com.nebula.erp.sales.model.Sales;
import com.nebula.erp.sales.model.SalesItem;
import com.nebula.erp.sales.repository.PaymentHistoryRepository;
import com.nebula.erp.sales.repository.PaymentsRepository;
import com.nebula.erp.sales.repository.SalesItemRepository;
import com.nebula.erp.sales.repository.SalesRepository;
import com.nebula.erp.sales.requestmodel.PatientBalanceResponse;
import com.nebula.erp.sales.requestmodel.PaymentCreditRequest;
import com.nebula.erp.sales.requestmodel.PaymentDebitRequest;
import com.nebula.erp.sales.requestmodel.SalesItemRequest;
import com.nebula.erp.sales.requestmodel.SalesRequest;
import com.nebula.erp.sales.utility.CreateLogger;
import com.nebula.erp.sales.utility.JwtRequestUtils;
import org.apache.commons.lang3.ObjectUtils;
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.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.data.domain.Sort;
import com.nebula.erp.sales.model.PrescriptionItem;
import com.nebula.erp.sales.repository.PrescriptionItemRepository;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;

@Service
public class SalesService {

    @Autowired
    private SalesRepository salesRepository;

    @Autowired
    private SalesItemRepository salesItemRepository;

    @Autowired
    private PaymentsRepository paymentsRepository;

    @Autowired
    private PrescriptionItemRepository prescriptionItemRepository;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private PaymentHistoryService paymentHistoryService;

    @Autowired
    private PaymentHistoryRepository paymentHistoryRepository;

    @Value("${coupon.api}")
    private String couponAPI;

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

    @Value("${reduce.stock.api}")
    private String reduceStockAPI;

    @Value("${service.api}")
    private String serviceAPI;

    @Value("${inventory.api}")
    private String inventoryAPI;

    @Value("${package.api}")
    private String packageAPI;

    @Value("${room.api}")
    private String roomAPI;

    @Autowired
    private JwtRequestUtils jwtRequestUtils;

    @Autowired
    private CreateLogger createLogger;

    private static final String path = "/sales";

    public Page<Sales> getAllSales(int page, int size, String type, String search) {

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

        String tenant = jwtRequestUtils.getTenantName();

        // No search → normal flow
        if (search == null || search.trim().isEmpty()) {

            if ("product".equalsIgnoreCase(type)) {
                return salesRepository.findProductSales(tenant, pageable);
            }

            if ("combined".equalsIgnoreCase(type)) {
                return salesRepository.findCombinedSales(tenant, pageable);
            }

            return salesRepository.findAllByTenant(tenant, pageable);
        }

        search = search.trim();
        if (search.matches("\\d+(\\.\\d+)?")) {
            Long id = null;
            Double amount = null;
            if (!search.contains(".")) {
                try {
                    id = Long.parseLong(search);
                } catch (Exception ignored) {
                }
            }

            try {
                amount = Double.parseDouble(search);
            } catch (Exception ignored) {
            }

            if ("product".equalsIgnoreCase(type)) {
                return salesRepository.searchProductSalesByNumbers(
                        tenant, id, amount, pageable);
            }

            if ("combined".equalsIgnoreCase(type)) {
                return salesRepository.searchCombinedSalesByNumbers(
                        tenant, id, amount, pageable);
            }

            return salesRepository.searchAllSalesByNumbers(
                    tenant, id, amount, pageable);
        }
        String keyword = "%" + search.toLowerCase() + "%";

        if ("product".equalsIgnoreCase(type)) {
            return salesRepository.searchProductSales(tenant, keyword, pageable);
        }

        if ("combined".equalsIgnoreCase(type)) {
            return salesRepository.searchCombinedSales(tenant, keyword, pageable);
        }

        return salesRepository.searchAllSales(tenant, keyword, pageable);
    }

    public Sales getSaleByPrescriptionId(String prescriptionId) {
        return salesRepository.findSaleByPrescriptionId(prescriptionId);
    }

    public Integer getSalesCount(String tenant_name) {
        return salesRepository.findCountByTenant(tenant_name);
    }

    private double roundToTwo(double value) {
        if (Math.abs(value) < 0.00001) {
            return 0.0;
        }
        return Math.round(value * 100.0) / 100.0;
    }

    private double calculateAdvanceAvailable(String patientId, String tenant) {
        double originalAdvance = paymentHistoryRepository.findOriginalAdvance(patientId, tenant);
        double totalRefund = Math.abs(paymentHistoryRepository.findTotalRefund(patientId, tenant));
        List<PaymentHistory> ledger = paymentHistoryRepository.findByPatient(patientId, tenant);
        double advanceUsedPreviously = ledger.stream()
                .filter(entry -> entry.getDebit() > 0 && entry.getSales_id() != null)
                .mapToDouble(entry -> {
                    long saleId = entry.getSales_id();
                    double saleAmount = entry.getDebit();
                    double paidForSale = paymentHistoryRepository.findTotalPaidForSale(saleId);
                    double used = saleAmount - paidForSale;
                    return Math.max(used, 0);
                })
                .sum();
        double remaining = originalAdvance - advanceUsedPreviously - totalRefund;

        return Math.max(remaining, 0);
    }

    // Create a new Sales with Sales Items and Payments
    @Transactional
    public Sales createSales(SalesRequest salesRequest) {
        // Extract Authorization headers from the request
        HttpHeaders headers = jwtRequestUtils.getAuthorizationHeaders();

        // Create an HttpEntity with the extracted headers
        HttpEntity<String> entity = new HttpEntity<>(headers);

        String tenantName = jwtRequestUtils.getTenantName();
        String userId = jwtRequestUtils.getUserId();

        try {
            // Check if there is at least one Sales item
            List<SalesItemRequest> salesItems = salesRequest.getSales_items();
            if (salesItems == null || salesItems.isEmpty()) {
                createLogger.createLogger("error", path, "POST", "At least one Sales item is required", "");
                throw new IllegalArgumentException("At least one Sales item is required");
            }
            Map<String, Object> coupon[] = new Map[1]; // Use an array wrapper to hold the coupon map
            double couponPercentage = 0.00;

            // Get coupon details if any
            if (salesRequest.getCoupon_code() != null && !salesRequest.getCoupon_code().isEmpty()) {
                Map<String, Object> couponData = restTemplate
                        .exchange(couponAPI + salesRequest.getCoupon_code(), HttpMethod.GET, entity, Map.class)
                        .getBody();
                List<Map<String, Object>> coupons = (List<Map<String, Object>>) ((Map<String, Object>) couponData
                        .get("data")).get("items");

                if (coupons != null && !coupons.isEmpty() && coupons.get(0) != null) {
                    coupon[0] = coupons.get(0);

                    // Parse the start_date and end_date
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // Adjust the pattern to
                                                                                             // match your date format
                    LocalDate startDate = LocalDate.parse((String) coupon[0].get("start_date"), formatter);
                    LocalDate endDate = LocalDate.parse((String) coupon[0].get("end_date"), formatter);

                    // Check if today is within the range
                    LocalDate today = LocalDate.now();
                    if (today.isBefore(startDate) || today.isAfter(endDate)) {
                        throw new IllegalArgumentException("Coupon is expired or not valid.");
                    } else {
                        couponPercentage = (Double) coupon[0].get("percentage");

                    }
                } else {
                    throw new IllegalArgumentException("Entered coupon not found.");
                }
            }

            // Create Sales object
            Sales sales = new Sales();
            sales.setDate(salesRequest.getDate());
            sales.setPrescription_id(salesRequest.getPrescription_id());
            sales.setEncounter_id(salesRequest.getEncounter_id());
            sales.setCustomEncounterId(salesRequest.getCustom_encounter_id());
            sales.setPatient_id(salesRequest.getPatient_id());
            sales.setMrn_no(salesRequest.getMrn_no());
            sales.setPatient_name(salesRequest.getPatient_name());
            sales.setCustomer_name(salesRequest.getCustomer_name());
            sales.setCustomer_mobile(salesRequest.getCustomer_mobile());
            sales.setCustomer_email(salesRequest.getCustomer_email());
            sales.setCustomer_dob(salesRequest.getCustomer_dob());
            sales.setCustomer_gender(salesRequest.getCustomer_gender());
            sales.setCustomer_address(salesRequest.getCustomer_address());
            sales.setPayment_status(salesRequest.getPayment_status());
            sales.setPayment_method(salesRequest.getPayment_method());
            sales.setPayment_sub_method(salesRequest.getPayment_sub_method());
            // sales.setPayment_status("UNPAID")
            sales.setCreated_by(userId);
            sales.setTenant(tenantName);

            final double[] totalAmount = { 0 };
            List<String> insufficientStockProducts = new ArrayList<>();
            // Create Sales Items
            List<SalesItem> salesItemList = salesItems.stream().flatMap(itemRequest -> {
                if ("service".equals(itemRequest.getType())) {
                    SalesItem salesItem = new SalesItem();
                    salesItem.setPrescription_item_id(itemRequest.getPrescription_item_id());
                    salesItem.setSales(sales);
                    salesItem.setType(itemRequest.getType());
                    salesItem.setProduct_id(itemRequest.getProduct_id());
                    salesItem.setQuantity(itemRequest.getQuantity());

                    if (ObjectUtils.isEmpty(itemRequest.getProduct_id())) {
                        salesItem.setProduct_name(itemRequest.getProduct_name());
                        salesItem.setUnit_price(Double.valueOf(itemRequest.getProduct_price()));
                        salesItem.setTotal_price(Double.valueOf(itemRequest.getProduct_price()));
                        totalAmount[0] += Double.valueOf(itemRequest.getProduct_price());

                        salesItem.setTax_id(null);
                        salesItem.setCreated_by(userId);
                        salesItem.setTenant(tenantName);
                    } else {
                        // Get service details
                        Map<String, Object> serviceData = restTemplate
                                .exchange(serviceAPI + itemRequest.getProduct_id(), HttpMethod.GET, entity, Map.class)
                                .getBody();
                        Map<String, Object> serviceItem = (Map<String, Object>) serviceData.get("data");
                        // Calculate service and GST amounts
                        double service_rate = ((Number) serviceItem.get("rate")).doubleValue();
                        double service_tax_rate = ((Number) serviceItem.get("tax_rate")).doubleValue();
                        double service_quantity = Double.valueOf(itemRequest.getQuantity());
                        double serviceAmount = service_rate * service_quantity;
                        double serviceGstAmount = serviceAmount * service_tax_rate / 100;
                        salesItem.setUnit_price(service_rate);
                        salesItem.setTotal_price(serviceAmount + serviceGstAmount);
                        totalAmount[0] += serviceAmount + serviceGstAmount;
                        salesItem.setProduct_name(
                                serviceItem.get("service_name") != null
                                        ? serviceItem.get("service_name").toString()
                                        : itemRequest.getProduct_name());

                        salesItem.setTax_id(((Number) serviceItem.get("tax_id")).longValue());
                        salesItem.setCreated_by(userId);
                        salesItem.setTenant(tenantName);
                    }

                    return Stream.of(salesItem);
                } else if ("package".equals(itemRequest.getType())) {
                    SalesItem salesItem = new SalesItem();
                    salesItem.setPrescription_item_id(itemRequest.getPrescription_item_id());
                    salesItem.setSales(sales);
                    salesItem.setType(itemRequest.getType());
                    salesItem.setProduct_id(itemRequest.getProduct_id());
                    salesItem.setQuantity(itemRequest.getQuantity());

                    // Get pacakge details
                    Map<String, Object> packageData = restTemplate
                            .exchange(packageAPI + itemRequest.getProduct_id(), HttpMethod.GET, entity, Map.class)
                            .getBody();
                    Map<String, Object> packageItem = (Map<String, Object>) packageData.get("data");

                    // Calculate service and GST amounts
                    double package_rate = ((Number) packageItem.get("final_amount")).doubleValue();
                    double package_quantity = Double.valueOf(itemRequest.getQuantity());
                    double packageAmount = package_rate * package_quantity;

                    salesItem.setUnit_price(package_rate);
                    salesItem.setTotal_price(packageAmount);
                    totalAmount[0] += packageAmount;
                    // FIX: Set product_name from API or fallback
                    salesItem.setProduct_name(
                            packageItem.get("package_name") != null
                                    ? packageItem.get("package_name").toString()
                                    : itemRequest.getProduct_name());

                    salesItem.setCreated_by(userId);
                    salesItem.setTenant(tenantName);

                    return Stream.of(salesItem);
                } else if ("bed".equals(itemRequest.getType())) {

                    if (itemRequest.getProduct_id() == null || itemRequest.getProduct_id().trim().isEmpty()) {
                        throw new IllegalArgumentException("Room Location ID is required for bed sales.");
                    }

                    try {
                        // Fetch all admission rooms
                        String roomUrl = roomAPI + "?limit=1000&room_category=admission";
                        Map<String, Object> roomResponse = restTemplate
                                .exchange(roomUrl, HttpMethod.GET, entity, Map.class)
                                .getBody();

                        Map<String, Object> data = (Map<String, Object>) roomResponse.get("data");
                        List<Map<String, Object>> items = (List<Map<String, Object>>) data.get("items");

                        if (items == null || items.isEmpty()) {
                            throw new IllegalArgumentException("No rooms available");
                        }

                        // Find the room by location_id
                        Map<String, Object> room = items.stream()
                                .filter(r -> r.get("location_id") != null &&
                                        r.get("location_id").toString().equals(itemRequest.getProduct_id().trim()))
                                .findFirst()
                                .orElse(null);
                        SalesItem salesItem = new SalesItem();
                        salesItem.setPrescription_item_id(itemRequest.getPrescription_item_id());
                        salesItem.setSales(sales);
                        salesItem.setType("bed");
                        salesItem.setProduct_id(itemRequest.getProduct_id());
                        salesItem.setLocation_id(itemRequest.getProduct_id());
                        salesItem.setTax_id(null);
                        salesItem.setTax_rate(0.0);
                        salesItem.setQuantity(itemRequest.getQuantity());
                        salesItem.setTenant(tenantName);
                        salesItem.setCreated_by(userId);
                        if (room == null) {
                            salesItem.setRoom_id(null);
                            salesItem.setProduct_name(
                                    itemRequest.getProduct_name() != null ? itemRequest.getProduct_name() : "Room");
                            double unitPrice = 0.0;
                            if (itemRequest.getProduct_price() != null && !itemRequest.getProduct_price().isEmpty()) {
                                try {
                                    unitPrice = Double.parseDouble(itemRequest.getProduct_price());
                                } catch (NumberFormatException e) {
                                    unitPrice = 0.0;
                                }
                            }

                            int quantity = itemRequest.getQuantity();
                            double totalPrice = unitPrice * quantity;
                            salesItem.setUnit_price(unitPrice);
                            salesItem.setQuantity(quantity);
                            salesItem.setTotal_price(totalPrice);
                            salesItem.setTax_id(null);
                            salesItem.setTax_rate(0.0);
                            totalAmount[0] += totalPrice;

                            return Stream.of(salesItem);
                        }

                        // Room found in API - extract details
                        long roomId = Long.parseLong(room.get("id").toString());
                        String locationId = room.get("location_id").toString();
                        String roomName = room.get("room_name") != null ? room.get("room_name").toString() : "Room";
                        String roomNumber = room.get("room_number") != null ? room.get("room_number").toString() : null;

                        // PRIORITY: Use prescription price if available, else API price
                        double bedRate = 0.0;

                        // Try prescription price first
                        if (itemRequest.getProduct_price() != null && !itemRequest.getProduct_price().isEmpty()) {
                            try {
                                double prescriptionPrice = Double.parseDouble(itemRequest.getProduct_price());
                                if (prescriptionPrice > 0) {
                                    bedRate = prescriptionPrice;
                                }
                            } catch (NumberFormatException e) {
                                // Fall through to API price
                            }
                        }

                        // If still 0, use API price
                        if (bedRate == 0.0 && room.get("bed_rate") != null) {
                            try {
                                bedRate = Double.parseDouble(room.get("bed_rate").toString());
                            } catch (NumberFormatException e) {
                                bedRate = 0.0;
                            }
                        }

                        int days = itemRequest.getQuantity();
                        double amount = bedRate * days;

                        // Extract tax from matched room
                        Object taxIdObj = room.get("tax_id");
                        Object taxRateObj = room.get("tax_rate");
                        Long taxId = taxIdObj != null ? Long.parseLong(taxIdObj.toString()) : null;
                        Double taxRate = taxRateObj != null ? Double.parseDouble(taxRateObj.toString()) : 0.0;

                        double taxAmount = amount * taxRate / 100;
                        double totalWithTax = amount + taxAmount;

                        salesItem.setRoom_id(roomId);
                        salesItem.setLocation_id(locationId);
                        salesItem.setProduct_name(roomName);
                        salesItem.setUnit_price(bedRate);
                        salesItem.setQuantity(days);
                        salesItem.setTax_id(taxId);
                        salesItem.setTax_rate(taxRate);
                        salesItem.setTotal_price(totalWithTax);

                        totalAmount[0] += totalWithTax;
                        return Stream.of(salesItem);

                    } catch (Exception e) {
                        // If API fails completely, use prescription data
                        SalesItem salesItem = new SalesItem();
                        salesItem.setPrescription_item_id(itemRequest.getPrescription_item_id());
                        salesItem.setSales(sales);
                        salesItem.setType("bed");
                        salesItem.setProduct_id(itemRequest.getProduct_id());
                        salesItem.setRoom_id(null);
                        salesItem.setLocation_id(itemRequest.getProduct_id());
                        salesItem.setProduct_name(
                                itemRequest.getProduct_name() != null ? itemRequest.getProduct_name() : "Room");

                        // Convert String to double safely
                        double unitPrice = 0.0;
                        if (itemRequest.getProduct_price() != null && !itemRequest.getProduct_price().isEmpty()) {
                            try {
                                unitPrice = Double.parseDouble(itemRequest.getProduct_price());
                            } catch (NumberFormatException ex) {
                                unitPrice = 0.0;
                            }
                        }

                        int quantity = itemRequest.getQuantity();
                        double totalPrice = unitPrice * quantity;

                        salesItem.setUnit_price(unitPrice);
                        salesItem.setQuantity(quantity);
                        salesItem.setTotal_price(totalPrice);
                        salesItem.setTax_id(null);
                        salesItem.setTax_rate(0.0);
                        salesItem.setTenant(tenantName);
                        salesItem.setCreated_by(userId);

                        totalAmount[0] += totalPrice;

                        return Stream.of(salesItem);
                    }

                } else if ("product".equals(itemRequest.getType())) {

                    boolean isFromPrescription = itemRequest.getPrescription_item_id() != null;

                    Map<String, Object> product[] = new Map[1];
                    List<Map<String, Object>> responseList;

                    // 🔹 Fetch product master (price, tax) – ALWAYS needed
                    Map<String, Object> productData = restTemplate.exchange(
                            inventoryAPI + "&product_id=" + itemRequest.getProduct_id() + "&page=0&size=1000",
                            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 IllegalArgumentException("Product not found in inventory");
                    }

                    // product[0] = products.get(0);
                    product[0] = products.stream()
                            .filter(p -> String.valueOf(p.get("product_id"))
                                    .equals(itemRequest.getProduct_id()))
                            .findFirst()
                            .orElseThrow(() -> new IllegalArgumentException("Product not found in inventory"));
                    if (!isFromPrescription) {

                        if (itemRequest.getQuantity() > (Integer) product[0].get("total_quantity")) {
                            insufficientStockProducts.add(product[0].get("product_name") != null
                                    ? product[0].get("product_name").toString()
                                    : itemRequest.getProduct_name());
                            return Stream.empty();
                        }
                        Map<String, Object> stockData = new HashMap<>();
                        stockData.put("product_id", itemRequest.getProduct_id());
                        stockData.put("quantity", itemRequest.getQuantity());
                        stockData.put("reason", "SALES");

                        HttpEntity<Map<String, Object>> reduceEntity = new HttpEntity<>(stockData, headers);

                        ResponseEntity<Map> stockApiResponse;

                        try {

                            stockApiResponse = restTemplate.exchange(
                                    reduceStockAPI,
                                    HttpMethod.POST,
                                    reduceEntity,
                                    Map.class);

                        } catch (HttpClientErrorException ex) {

                            if (ex.getResponseBodyAsString().contains("Insufficient stock")) {

                                String productName = product[0].get("product_name") != null
                                        ? product[0].get("product_name").toString()
                                        : "Unknown Product";
                                throw new IllegalArgumentException(
                                        "Insufficient stock for \"" + productName + "\": " +
                                                "requested " + itemRequest.getQuantity() + ", available "
                                                + product[0].get("total_quantity"));
                            }

                            throw ex; // rethrow other errors
                        }

                        responseList = (List<Map<String, Object>>) stockApiResponse.getBody().get("data");
                    } else {
                        PrescriptionItem prescriptionItem = prescriptionItemRepository
                                .findById(itemRequest.getPrescription_item_id())
                                .orElseThrow(() -> new IllegalArgumentException("Prescription item not found"));

                        Map<String, Object> stockData = new HashMap<>();
                        stockData.put("product_id", itemRequest.getProduct_id());
                        stockData.put("quantity", itemRequest.getQuantity());
                        stockData.put("reason", "SALES");

                        // If batch exists → use it
                        if (prescriptionItem.getBatch_code() != null) {
                            stockData.put("batch_code", prescriptionItem.getBatch_code());
                        }

                        HttpEntity<Map<String, Object>> reduceEntity = new HttpEntity<>(stockData, headers);
                        ResponseEntity<Map> stockApiResponse;

                        try {

                            stockApiResponse = restTemplate.exchange(
                                    reduceStockAPI,
                                    HttpMethod.POST,
                                    reduceEntity,
                                    Map.class);

                        } catch (HttpClientErrorException ex) {

                            if (ex.getResponseBodyAsString().contains("Insufficient stock")) {

                                String productName = product[0].get("product_name") != null
                                        ? product[0].get("product_name").toString()
                                        : "Unknown Product";

                                throw new IllegalArgumentException(
                                        "Insufficient stock for \"" + productName + "\": " +
                                                "requested " + itemRequest.getQuantity() + ", available "
                                                + product[0].get("total_quantity"));
                            }

                            throw ex; // rethrow other errors
                        }

                        responseList = (List<Map<String, Object>>) stockApiResponse.getBody().get("data");
                    }

                    return responseList.stream().map(stockItem -> {

                        SalesItem salesItem = new SalesItem();
                        salesItem.setPrescription_item_id(itemRequest.getPrescription_item_id());
                        salesItem.setSales(sales);
                        salesItem.setType("product");
                        salesItem.setProduct_id(itemRequest.getProduct_id());
                        salesItem.setQuantity((Integer) stockItem.get("quantity"));
                        Object batchCode = stockItem.get("batch_code");
                        salesItem.setBatch_code(batchCode != null ? batchCode.toString() : null);

                        double productAmount = ((Double) product[0].get("unit_price"))
                                * (Integer) stockItem.get("quantity");
                        double gstAmount = productAmount * ((Double) product[0].get("tax_rate")) / 100;

                        salesItem.setUnit_price((Double) product[0].get("unit_price"));
                        salesItem.setTotal_price(productAmount + gstAmount);
                        totalAmount[0] += productAmount + gstAmount;

                        salesItem.setProduct_name(
                                product[0].get("product_name") != null
                                        ? product[0].get("product_name").toString()
                                        : itemRequest.getProduct_name());

                        salesItem.setTax_id(((Number) product[0].get("tax_id")).longValue());
                        salesItem.setCreated_by(userId);
                        salesItem.setTenant(tenantName);

                        return salesItem;
                    });
                }
                return Stream.empty();
            }).collect(Collectors.toList());
            // Throw combined error if any products had insufficient stock
            if (!insufficientStockProducts.isEmpty()) {
                throw new IllegalArgumentException(
                        "Insufficient stock for: " + String.join(", ", insufficientStockProducts));
            }
            // Link SalesItems to Sales
            sales.setSales_items(salesItemList);
            if (couponPercentage > 0) {
                double couponAmount = totalAmount[0] * couponPercentage / 100;
                totalAmount[0] = totalAmount[0] - couponAmount;
                sales.setCoupon_amount(couponAmount);
                sales.setCoupon_code(salesRequest.getCoupon_code());
            }
            if (salesRequest.getDiscount() != null) {
                if (salesRequest.getDiscount() > totalAmount[0]) {
                    throw new IllegalArgumentException("Discount should not exceed total sale amount.");
                }
                totalAmount[0] = totalAmount[0] - salesRequest.getDiscount();
                sales.setDiscount(salesRequest.getDiscount());
            }
            // // ---- GET PREVIOUS DUE ----
            PatientBalanceResponse bal = paymentHistoryService.getBalance(salesRequest.getPatient_id());

            double previousDue = 0.0;
            if ("DUE".equals(bal.getTotal_balance_type())) {
                previousDue = bal.getTotal_balance_value();
            }

            if (("razorpay".equalsIgnoreCase(salesRequest.getPayment_method())
                    || "RAZORPAY_WHATSAPP".equalsIgnoreCase(salesRequest.getPayment_method())
                    || "RAZORPAY_SMS".equalsIgnoreCase(salesRequest.getPayment_method()))
                    && salesRequest.getAmount_paid() != null) {
                throw new IllegalArgumentException("Do not pass amount_paid for Razorpay payments");
            }

            double prescriptionTotal = totalAmount[0]; // already calculated
            sales.setPrescriptionTotalAmount(prescriptionTotal);

            // ---- NEW SALE AMOUNT ONLY ----
            sales.setTotal_amount(totalAmount[0]);

            // Now save the Sales object to generate an ID
            Sales savedSales = salesRepository.save(sales);
            List<Long> prescriptionItemIds = salesRequest.getSales_items()
                    .stream()
                    .map(SalesItemRequest::getPrescription_item_id)
                    .filter(id -> id != null && id > 0)
                    .collect(Collectors.toList());

            if (!prescriptionItemIds.isEmpty()) {

                List<PrescriptionItem> prescriptionItems = prescriptionItemRepository.findAllById(prescriptionItemIds);
                for (PrescriptionItem item : prescriptionItems) {
                    if (Boolean.TRUE.equals(item.getIs_sale())) {
                        throw new IllegalStateException(
                                "Prescription item already billed. ID: " + item.getId());
                    }
                }

                // MARK ITEMS AS SOLD
                for (PrescriptionItem item : prescriptionItems) {
                    item.setIs_sale(true);
                    item.setUpdated_at(LocalDateTime.now());
                }

                prescriptionItemRepository.saveAll(prescriptionItems);
            }

            double billAmount = savedSales.getTotal_amount();
            double paidNow = salesRequest.getAmount_paid() == null ? 0.0 : salesRequest.getAmount_paid();
            // Determine billing type based on items

            boolean isPharmacyBilling = savedSales.getSales_items()
                    .stream()
                    .allMatch(item -> "product".equalsIgnoreCase(item.getType()));

            boolean isHospitalBilling = salesItemList.stream()
                    .anyMatch(item -> "service".equals(item.getType()) ||
                            "package".equals(item.getType()) ||
                            "bed".equals(item.getType()));
            // Load existing ledger entries (INITIAL – only to check debit)
            List<PaymentHistory> existing = paymentHistoryRepository.findBySalesIdAndTenant(
                    savedSales.getId(), tenantName);

            boolean hasDebit = existing.stream()
                    .anyMatch(e -> "DEBIT".equals(e.getEntry_type()));

            boolean hasAdvanceUsed = existing.stream()
                    .anyMatch(e -> "ADVANCE_USED".equals(e.getEntry_type()));
            ;
            boolean alreadyHasPayment = existing.stream()
                    .anyMatch(e -> "PAYMENT".equals(e.getEntry_type()) && Double.compare(e.getCredit(), paidNow) == 0);

            if (isPharmacyBilling) {

                // Always create debit first
                if (!hasDebit) {
                    PaymentDebitRequest debitReq = new PaymentDebitRequest();
                    debitReq.setPatient_id(savedSales.getPatient_id());
                    debitReq.setMrn_no(savedSales.getMrn_no());
                    debitReq.setSales_id(savedSales.getId());
                    debitReq.setEncounter_id(savedSales.getEncounter_id());
                    debitReq.setCustom_encounter_id(savedSales.getCustomEncounterId());
                    debitReq.setAmount(billAmount);

                    paymentHistoryService.addDebit(debitReq);
                }

                boolean isRazorpay = "razorpay".equalsIgnoreCase(savedSales.getPayment_method())
                        || "RAZORPAY_WHATSAPP".equalsIgnoreCase(savedSales.getPayment_method())
                        || "RAZORPAY_SMS".equalsIgnoreCase(savedSales.getPayment_method());

                // 🔹 CASH FLOW
                if (!isRazorpay) {

                    if (roundToTwo(paidNow) != roundToTwo(billAmount)) {
                        throw new IllegalArgumentException(
                                "Pharmacy sales must be fully paid.");
                    }

                    PaymentCreditRequest payReq = new PaymentCreditRequest();
                    payReq.setPatient_id(savedSales.getPatient_id());
                    payReq.setMrn_no(savedSales.getMrn_no());
                    payReq.setSales_id(savedSales.getId());
                    payReq.setEncounter_id(savedSales.getEncounter_id());
                    payReq.setCustom_encounter_id(savedSales.getCustomEncounterId());
                    payReq.setAmount(billAmount);

                    paymentHistoryService.addCredit(payReq);

                    savedSales.setTotal_paid_for_sale(roundToTwo(billAmount));
                    savedSales.setRemaining_due_amount(0.0);
                    savedSales.setPayment_status("PAID");
                }

                // RAZORPAY FLOW
                else {

                    savedSales.setTotal_paid_for_sale(0.0);
                    savedSales.setRemaining_due_amount(roundToTwo(billAmount));
                    savedSales.setPayment_status("UNPAID");
                }

                salesRepository.save(savedSales);
                return savedSales;
            }
            // HOSPITAL BILLING — OLD CORRECT LOGIC
            if (isHospitalBilling) {
                if (!hasDebit) {
                    PaymentDebitRequest debitReq = new PaymentDebitRequest();
                    debitReq.setPatient_id(savedSales.getPatient_id());
                    debitReq.setMrn_no(savedSales.getMrn_no());
                    debitReq.setSales_id(savedSales.getId());
                    debitReq.setEncounter_id(savedSales.getEncounter_id());
                    debitReq.setCustom_encounter_id(savedSales.getCustomEncounterId());
                    debitReq.setAmount(billAmount);
                    paymentHistoryService.addDebit(debitReq);
                }

                // 2️⃣ Calculate available advance
                double totalAdvance = paymentHistoryRepository.findOriginalAdvance(
                        savedSales.getPatient_id(), tenantName);

                double totalRefund = paymentHistoryRepository.findTotalRefund(
                        savedSales.getPatient_id(), tenantName);

                List<PaymentHistory> ledger = paymentHistoryRepository.findByPatient(
                        savedSales.getPatient_id(), tenantName);

                double totalAdvanceUsed = ledger.stream()
                        .filter(e -> "ADVANCE_USED".equals(e.getEntry_type()))
                        .mapToDouble(PaymentHistory::getAdvance_used)
                        .sum();

                double availableAdvance = Math.max(
                        totalAdvance - totalAdvanceUsed - totalRefund, 0);

                // 3️⃣ Apply advance ONLY to current sale
                double advanceUsedNow = Math.min(availableAdvance, billAmount);

                if (advanceUsedNow > 0 && !hasAdvanceUsed) {
                    PaymentDebitRequest advReq = new PaymentDebitRequest();
                    advReq.setPatient_id(savedSales.getPatient_id());
                    advReq.setMrn_no(savedSales.getMrn_no());
                    advReq.setSales_id(savedSales.getId());
                    advReq.setEncounter_id(savedSales.getEncounter_id());
                    advReq.setCustom_encounter_id(savedSales.getCustomEncounterId());
                    advReq.setAdvance_used(advanceUsedNow);

                    paymentHistoryService.addAdvanceUsed(advReq);
                }

                double remainingCash = paidNow;
                double cashUsedForCurrentSale = 0.0;

                /* STEP 4A: SETTLE PREVIOUS DUES USING CASH */
                if (remainingCash > 0) {

                    List<Sales> oldSales = salesRepository.findPendingSalesForSettlement(
                            savedSales.getPatient_id(),
                            tenantName,
                            savedSales.getId());

                    for (Sales oldSale : oldSales) {

                        if (remainingCash <= 0)
                            break;

                        double oldPaid = paymentHistoryRepository
                                .findTotalPaidForSale(oldSale.getId());

                        double oldDue = oldSale.getTotal_amount() - oldPaid;
                        if (oldDue <= 0)
                            continue;

                        double settleAmount = Math.min(oldDue, remainingCash);

                        PaymentCreditRequest settleReq = new PaymentCreditRequest();
                        settleReq.setPatient_id(oldSale.getPatient_id());
                        settleReq.setMrn_no(oldSale.getMrn_no());
                        settleReq.setSales_id(oldSale.getId());
                        settleReq.setEncounter_id(oldSale.getEncounter_id());
                        settleReq.setCustom_encounter_id(oldSale.getCustomEncounterId());
                        settleReq.setAmount(settleAmount);

                        paymentHistoryService.addCredit(settleReq);

                        remainingCash -= settleAmount;

                        double oldRemaining = oldSale.getTotal_amount() - oldPaid;
                        oldSale.setPayment_status(
                                oldRemaining <= 0 ? "PAID" : "PARTIAL");

                        salesRepository.save(oldSale);
                    }
                }

                /* STEP 4B: APPLY CASH TO CURRENT SALE ONLY */
                double maxPayableForCurrent = billAmount - advanceUsedNow;
                cashUsedForCurrentSale = Math.min(remainingCash, maxPayableForCurrent);

                if (cashUsedForCurrentSale > 0) {

                    PaymentCreditRequest payReq = new PaymentCreditRequest();
                    payReq.setPatient_id(savedSales.getPatient_id());
                    payReq.setMrn_no(savedSales.getMrn_no());
                    payReq.setSales_id(savedSales.getId());
                    payReq.setEncounter_id(savedSales.getEncounter_id());
                    payReq.setCustom_encounter_id(savedSales.getCustomEncounterId());
                    payReq.setAmount(cashUsedForCurrentSale);

                    paymentHistoryService.addCredit(payReq);
                }

                double remaining = billAmount - advanceUsedNow - cashUsedForCurrentSale;
                remaining = roundToTwo(remaining);
                savedSales.setPrevious_due_paid(
                        paidNow - cashUsedForCurrentSale > 0 ? paidNow - cashUsedForCurrentSale : 0.0);
                savedSales.setTotal_paid_for_sale(cashUsedForCurrentSale + advanceUsedNow);
                savedSales.setRemaining_due_amount(Math.max(remaining, 0.0));

                boolean isRazorpay = "razorpay".equalsIgnoreCase(savedSales.getPayment_method()) ||
                        "RAZORPAY_WHATSAPP".equalsIgnoreCase(savedSales.getPayment_method()) ||
                        "RAZORPAY_SMS".equalsIgnoreCase(savedSales.getPayment_method());

                if (isRazorpay) {

                    // Razorpay sale remains UNPAID until payment success
                    savedSales.setPayment_status("UNPAID");

                } else {

                    if (remaining <= 0) {
                        savedSales.setPayment_status("PAID");
                    } else if (cashUsedForCurrentSale > 0 || advanceUsedNow > 0) {
                        savedSales.setPayment_status("PARTIAL");
                    } else {
                        savedSales.setPayment_status("UNPAID");
                    }
                }

                salesRepository.save(savedSales);
                return savedSales;
            }
            throw new IllegalArgumentException("Mixed billing not allowed. Create separate bills.");
        } catch (IllegalArgumentException e) {
            throw e;
        }

    }

    public Sales save(Sales sale) {
        return salesRepository.save(sale);
    }

    // Retrieve Sales by ID
    public Optional<Sales> getSales(Long id) {
        String tenantName = jwtRequestUtils.getTenantName();
        return salesRepository.findByIdAndTenant(id, tenantName);
    }

    // Retrieve Sales by Patient ID
    public List<Sales> getSalesByPatientId(String patientId) {
        String tenantName = jwtRequestUtils.getTenantName();
        return salesRepository.findAllByPatientIdAndTenant(patientId, tenantName);
    }

    public Optional<Sales> getLatestSaleByEncounter(String encounterId) {

        String tenant = jwtRequestUtils.getTenantName();

        return salesRepository
                .findByEncounterIdAndTenantOrderByCreatedAtDesc(encounterId, tenant)
                .stream()
                .findFirst();
    }

    public Map<String, Object> getSalesTotals() {

        String tenant = jwtRequestUtils.getTenantName();

        double hospitalTotal = salesRepository.sumHospitalSales(tenant);
        double pharmacyTotal = salesRepository.sumPharmacySales(tenant);

        Map<String, Object> totals = new LinkedHashMap<>();
        totals.put("total_hospital_sales_amount", hospitalTotal);
        totals.put("total_pharmacy_sales_amount", pharmacyTotal);
        totals.put("total_sales_amount", hospitalTotal + pharmacyTotal);

        return totals;
    }

}