package org.example.petshopdesktop.controllers;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession;
import javafx.concurrent.Task;
import org.example.petshopdesktop.api.endpoints.CustomerApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.api.endpoints.CouponApi;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.coupon.CouponResponse;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TableViewSupport;

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

public class SaleController {

    @FXML
    private Button btnRefresh;

    @FXML
    private Label lblStatus;

    @FXML
    private Button btnRefund;

    @FXML
    private Label lblModeNote;

    @FXML
    private VBox vbCreateSale;

    @FXML
    private ComboBox<Product> cbProduct;

    @FXML
    private Spinner<Integer> spQuantity;

    @FXML
    private Button btnAddToCart;

    @FXML
    private Button btnRemoveSelected;

    @FXML
    private TableView<SaleCartItem> tvCart;

    @FXML
    private TableColumn<SaleCartItem, String> colCartProduct;

    @FXML
    private TableColumn<SaleCartItem, Integer> colCartQty;

    @FXML
    private TableColumn<SaleCartItem, Double> colCartUnitPrice;

    @FXML
    private TableColumn<SaleCartItem, Double> colCartTotal;

    @FXML
    private ComboBox<String> cbPaymentMethod;

    @FXML
    private Label lblCartTotal;

    @FXML
    private Button btnClearCart;

    @FXML
    private Button btnSaveSale;

    @FXML
    private TableColumn<SaleLineItem, Integer> colSaleId;

    @FXML
    private TableColumn<SaleLineItem, String> colSaleDate;

    @FXML
    private TableColumn<SaleLineItem, String> colEmployeeName;

    @FXML
    private TableColumn<SaleLineItem, String> colCustomerName;

    @FXML
    private TableColumn<SaleLineItem, String> colServiceProduct;

    @FXML
    private TableColumn<SaleLineItem, Integer> colSaleQuantity;

    @FXML
    private TableColumn<SaleLineItem, Double> colSaleUnitPrice;

    @FXML
    private TableColumn<SaleLineItem, Double> colSaleTotal;

    @FXML
    private TableColumn<SaleLineItem, String> colSalePaymentType;

    @FXML
    private TableColumn<SaleLineItem, String> colStoreName;

    @FXML
    private TableView<SaleLineItem> tvSales;

    @FXML
    private TextField txtSearch;

    @FXML
    private ComboBox<String> cbFilterPayment;

    @FXML
    private ComboBox<String> cbFilterRefundStatus;

    @FXML
    private ComboBox<DropdownOption> cbFilterCustomer;

    @FXML
    private ComboBox<DropdownOption> cbStoreFilter;

    private final java.util.ArrayList<DropdownOption> storeOptions = new java.util.ArrayList<>();

    @FXML
    private ComboBox<DropdownOption> cbCustomer;

    @FXML
    private CheckBox chkUseLoyaltyPoints;

    @FXML
    private Label lblLoyaltyPoints;

    @FXML
    private TextField txtCouponCode;

    @FXML
    private Button btnApplyCoupon;

    @FXML
    private Button btnClearCoupon;

    @FXML
    private Label lblCouponStatus;

    @FXML
    private Label lblSubtotal;

    @FXML
    private HBox hbCouponDiscount;

    @FXML
    private Label lblCouponDiscount;

    @FXML
    private HBox hbLoyaltyDiscount;

    @FXML
    private Label lblLoyaltyDiscountLabel;

    @FXML
    private Label lblLoyaltyDiscount;

    @FXML
    private HBox hbPointsToEarn;

    @FXML
    private Label lblPointsToEarn;

    private final ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
    private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
    private FilteredList<SaleLineItem> filteredSales;
    private boolean saleSaveInProgress;

    private CouponResponse appliedCoupon = null;
    private UserResponse selectedCustomerData = null;

    private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

    @FXML
    public void initialize() {
        setupTables();
        setupCreateSale();
        applyRoleMode();

        refreshSales();
    }

    private void setupTables() {
        tvCart.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
        tvCart.setFixedCellSize(34);
        colCartProduct.setCellValueFactory(new PropertyValueFactory<>("prodName"));
        colCartQty.setCellValueFactory(new PropertyValueFactory<>("quantity"));
        colCartUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
        colCartTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
        colCartProduct.setMinWidth(190);
        colCartQty.setMinWidth(70);
        colCartUnitPrice.setMinWidth(90);
        colCartTotal.setMinWidth(90);
        TableViewSupport.applyCurrencyColumn(colCartUnitPrice);
        TableViewSupport.applyCurrencyColumn(colCartTotal);
        tvCart.setItems(cartItems);
        tvCart.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);

        tvSales.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
        tvSales.setFixedCellSize(34);
        colSaleId.setCellValueFactory(new PropertyValueFactory<>("saleId"));
        colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate"));
        colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
        colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
        colCustomerName.setMinWidth(120);
        colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("itemName"));
        colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
        colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
        colSaleTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
        colSalePaymentType.setCellValueFactory(new PropertyValueFactory<>("paymentMethod"));
        colStoreName.setCellValueFactory(new PropertyValueFactory<>("storeName"));
        colSaleId.setMinWidth(50);
        colSaleDate.setMinWidth(150);
        colEmployeeName.setMinWidth(150);
        colServiceProduct.setMinWidth(260);
        colSaleQuantity.setMinWidth(55);
        colSaleUnitPrice.setMinWidth(100);
        colSaleTotal.setMinWidth(100);
        colSalePaymentType.setMinWidth(95);
        TableViewSupport.applyCurrencyColumn(colSaleUnitPrice);
        TableViewSupport.applyCurrencyColumn(colSaleTotal);

        filteredSales = new FilteredList<>(saleItems, s -> true);
        TableViewSupport.bindSortedItems(tvSales, filteredSales);
        TableViewSupport.installDoubleClickAction(tvSales, selected -> openSaleDetailDialog(selected.getSaleId()));

        if (UserSession.getInstance().isAdmin()) {
            cbStoreFilter.setVisible(true);
            cbStoreFilter.setManaged(true);
            loadStoreFilter();
        }
        cbStoreFilter.valueProperty().addListener((obs, o, n) -> refreshSales(false));

        cbFilterPayment.setItems(FXCollections.observableArrayList("All Payments", "Cash", "Card"));
        cbFilterPayment.getSelectionModel().selectFirst();
        cbFilterPayment.valueProperty().addListener((obs, o, n) -> applySalesFilter(txtSearch.getText()));

        cbFilterRefundStatus.setItems(FXCollections.observableArrayList("All Status", "Sales Only", "Refunds Only"));
        cbFilterRefundStatus.getSelectionModel().selectFirst();
        cbFilterRefundStatus.valueProperty().addListener((obs, o, n) -> applySalesFilter(txtSearch.getText()));

        loadCustomerFilter();
        cbFilterCustomer.valueProperty().addListener((obs, o, n) -> applySalesFilter(txtSearch.getText()));

        txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applySalesFilter(newVal));
        tvSales.widthProperty().addListener((obs, oldWidth, newWidth) -> updateSalesColumnWidths(newWidth.doubleValue()));
        tvCart.widthProperty().addListener((obs, oldWidth, newWidth) -> updateCartColumnWidths(newWidth.doubleValue()));
        Platform.runLater(() -> {
            updateSalesColumnWidths(tvSales.getWidth());
            updateCartColumnWidths(tvCart.getWidth());
        });
    }

    private void setupCreateSale() {
        spQuantity.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 999, 1));
        spQuantity.setEditable(true);

        cbPaymentMethod.setItems(FXCollections.observableArrayList("Cash", "Card"));
        cbPaymentMethod.getSelectionModel().selectFirst();

        updateCartTotal();

        setCreateSaleControlsDisabled(true);

        cbCustomer.valueProperty().addListener((obs, oldVal, newVal) -> {
            if (newVal != null) {
                loadCustomerDetails(newVal.getId());
            } else {
                selectedCustomerData = null;
                lblLoyaltyPoints.setVisible(false);
                lblLoyaltyPoints.setManaged(false);
                chkUseLoyaltyPoints.setVisible(false);
                chkUseLoyaltyPoints.setManaged(false);
                chkUseLoyaltyPoints.setSelected(false);
                updateCartTotal();
            }
        });

        chkUseLoyaltyPoints.selectedProperty().addListener((obs, oldVal, newVal) -> updateCartTotal());

        Task<ObservableList<Product>> productsTask = new Task<>() {
            @Override
            protected ObservableList<Product> call() throws Exception {
                List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
                ObservableList<Product> products = FXCollections.observableArrayList();
                for (ProductResponse pr : productResponses) {
                    products.add(new Product(
                        pr.getProdId().intValue(),
                        pr.getProdName(),
                        pr.getProdPrice().doubleValue(),
                        0,
                        pr.getProdDesc()
                    ));
                }
                return products;
            }
        };

        productsTask.setOnSucceeded(event -> {
            cbProduct.setItems(productsTask.getValue());
            setCreateSaleControlsDisabled(false);
        });

        productsTask.setOnFailed(event -> {
            Throwable e = productsTask.getException();
            ActivityLogger.getInstance().logException(
                "SaleController.setupCreateSale",
                e instanceof Exception ? (Exception) e : new RuntimeException(e),
                "Loading products"
            );
            setCreateSaleControlsDisabled(false);
            showError("Sales", "Could not load products. Check the backend connection and refresh the view.");
        });

        new Thread(productsTask).start();

        Task<List<DropdownOption>> customersTask = new Task<>() {
            @Override
            protected List<DropdownOption> call() throws Exception {
                return DropdownApi.getInstance().getCustomers();
            }
        };

        customersTask.setOnSucceeded(event -> {
            List<DropdownOption> customers = customersTask.getValue();
            ObservableList<DropdownOption> customerOptions = FXCollections.observableArrayList();
            customerOptions.addAll(customers);
            cbCustomer.setItems(customerOptions);
        });

        customersTask.setOnFailed(event -> {
            Throwable e = customersTask.getException();
            ActivityLogger.getInstance().logException(
                "SaleController.setupCreateSale",
                e instanceof Exception ? (Exception) e : new RuntimeException(e),
                "Loading customers"
            );
        });

        new Thread(customersTask).start();
    }

    private void loadCustomerDetails(Long customerId) {
        Task<UserResponse> task = new Task<>() {
            @Override
            protected UserResponse call() throws Exception {
                return CustomerApi.getInstance().getCustomerById(customerId);
            }
        };

        task.setOnSucceeded(event -> {
            selectedCustomerData = task.getValue();
            if (selectedCustomerData != null) {
                int pts = selectedCustomerData.getLoyaltyPoints() != null ? selectedCustomerData.getLoyaltyPoints() : 0;
                lblLoyaltyPoints.setText(pts + " pts available");
                lblLoyaltyPoints.setVisible(true);
                lblLoyaltyPoints.setManaged(true);
                boolean canRedeem = pts >= 20;
                chkUseLoyaltyPoints.setVisible(canRedeem);
                chkUseLoyaltyPoints.setManaged(canRedeem);
                if (!canRedeem) chkUseLoyaltyPoints.setSelected(false);
            } else {
                lblLoyaltyPoints.setVisible(false);
                lblLoyaltyPoints.setManaged(false);
                chkUseLoyaltyPoints.setVisible(false);
                chkUseLoyaltyPoints.setManaged(false);
                chkUseLoyaltyPoints.setSelected(false);
            }
            updateCartTotal();
        });

        task.setOnFailed(event -> {
            selectedCustomerData = null;
            Throwable e = task.getException();
            ActivityLogger.getInstance().logException(
                "SaleController.loadCustomerDetails",
                e instanceof Exception ? (Exception) e : new RuntimeException(e),
                "Loading customer details"
            );
        });

        new Thread(task).start();
    }

    private void applyRoleMode() {
        boolean isAdmin = UserSession.getInstance().isAdmin();
        vbCreateSale.setVisible(!isAdmin);
        vbCreateSale.setManaged(!isAdmin);
        btnRefund.setVisible(!isAdmin);
        btnRefund.setManaged(!isAdmin);
        lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
    }

    private void refreshSales() {
        refreshSales(false);
    }

    private void refreshSales(boolean showErrorDialog) {
        btnRefresh.setDisable(true);
        Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
            @Override
            protected List<SaleLineItem> call() throws Exception {
                List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null, selectedStoreId());
                sales.sort(Comparator.comparing(SaleResponse::getSaleDate, Comparator.nullsLast(Comparator.reverseOrder()))
                        .thenComparing(SaleResponse::getSaleId, Comparator.nullsLast(Comparator.reverseOrder())));
                List<SaleLineItem> lineItems = new ArrayList<>();

                for (SaleResponse sale : sales) {
                    String saleDate = sale.getSaleDate() != null
                        ? sale.getSaleDate().format(DATE_FORMATTER)
                        : "";

                    if (sale.getItems() != null && !sale.getItems().isEmpty()) {
                        double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
                        double saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0;
                        double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0;
                        for (SaleItemResponse item : sale.getItems()) {
                            boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
                            double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
                            int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
                            if (isRefund && quantity > 0) {
                                quantity = -quantity;
                            }
                            double lineTotal = unitPrice * quantity * discountRatio;
                            lineItems.add(new SaleLineItem(
                                sale.getSaleId().intValue(),
                                saleDate,
                                sale.getEmployeeName(),
                                item.getProductName(),
                                quantity,
                                unitPrice,
                                lineTotal,
                                sale.getPaymentMethod(),
                                isRefund,
                                sale.getStoreName(),
                                sale.getCustomerName()
                            ));
                        }
                    }
                }
                return lineItems;
            }
        };

        task.setOnSucceeded(event -> {
            saleItems.setAll(task.getValue());
            btnRefresh.setDisable(false);
        });

        task.setOnFailed(event -> {
            Throwable e = task.getException();
            ActivityLogger.getInstance().logException("SaleController.refreshSales", (Exception) e, "Loading sales");
            btnRefresh.setDisable(false);
            showError("Sales", "Could not load sales: " + e.getMessage());
        });

        new Thread(task).start();
    }

    @FXML
    void btnRefresh(ActionEvent event) {
        txtSearch.clear();
        cbFilterPayment.getSelectionModel().selectFirst();
        cbFilterRefundStatus.getSelectionModel().selectFirst();
        cbFilterCustomer.getSelectionModel().selectFirst();
        cbStoreFilter.getSelectionModel().selectFirst();
        TableViewSupport.clearSort(tvSales);
        refreshSales(false);
        TableViewSupport.flashStatus(lblStatus, "Refreshed");
    }

    @FXML
    void btnAddToCart(ActionEvent event) {
        Product product = cbProduct.getSelectionModel().getSelectedItem();
        if (product == null) {
            showError("Create Sale", "Select a product.");
            return;
        }

        int requestedQty;
        try {
            requestedQty = spQuantity.getValue();
        } catch (Exception e) {
            showError("Create Sale", "Enter a valid quantity.");
            return;
        }
        if (requestedQty <= 0) {
            showError("Create Sale", "Quantity must be at least 1.");
            return;
        }

        for (SaleCartItem item : cartItems) {
            if (item.getProdId() == product.getProdId()) {
                item.setQuantity(item.getQuantity() + requestedQty);
                tvCart.refresh();
                updateCartTotal();
                return;
            }
        }

        cartItems.add(new SaleCartItem(product.getProdId(), product.getProdName(), requestedQty, product.getProdPrice()));
        updateCartTotal();
    }

    @FXML
    void btnRemoveSelected(ActionEvent event) {
        SaleCartItem selected = tvCart.getSelectionModel().getSelectedItem();
        if (selected != null) {
            cartItems.remove(selected);
            updateCartTotal();
        }
    }

    @FXML
    void btnClearCart(ActionEvent event) {
        cartItems.clear();
        updateCartTotal();
    }

    @FXML
    void btnApplyCoupon(ActionEvent event) {
        String code = txtCouponCode.getText().trim();
        if (code.isEmpty()) {
            lblCouponStatus.setText("Enter a coupon code.");
            lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
            return;
        }

        BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());

        Task<CouponResponse> task = new Task<>() {
            @Override
            protected CouponResponse call() throws Exception {
                return CouponApi.getInstance().getCouponByCode(code);
            }
        };

        task.setOnSucceeded(evt -> {
            CouponResponse coupon = task.getValue();
            if (coupon == null) {
                lblCouponStatus.setText("Coupon not found.");
                lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
                return;
            }
            if (!Boolean.TRUE.equals(coupon.getActive())) {
                lblCouponStatus.setText("Coupon is not active.");
                lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
                return;
            }
            if (coupon.getMinOrderAmount() != null && subtotal.compareTo(coupon.getMinOrderAmount()) < 0) {
                lblCouponStatus.setText("Order minimum not met ($" + coupon.getMinOrderAmount() + ").");
                lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
                return;
            }
            appliedCoupon = coupon;
            txtCouponCode.setDisable(true);
            lblCouponStatus.setText("Coupon applied: " + coupon.getCouponCode());
            lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#27ae60"));
            updateCartTotal();
        });

        task.setOnFailed(evt -> {
            Throwable e = task.getException();
            lblCouponStatus.setText("Invalid or not found.");
            lblCouponStatus.setTextFill(javafx.scene.paint.Color.web("#e74c3c"));
            ActivityLogger.getInstance().logException(
                "SaleController.btnApplyCoupon",
                e instanceof Exception ? (Exception) e : new RuntimeException(e),
                "Applying coupon"
            );
        });

        new Thread(task).start();
    }

    @FXML
    void btnClearCoupon(ActionEvent event) {
        appliedCoupon = null;
        txtCouponCode.clear();
        txtCouponCode.setDisable(false);
        lblCouponStatus.setText("");
        hbCouponDiscount.setVisible(false);
        hbCouponDiscount.setManaged(false);
        updateCartTotal();
    }

    @FXML
    void btnSaveSale(ActionEvent event) {
        if (saleSaveInProgress) {
            return;
        }
        if (UserSession.getInstance().isAdmin()) {
            showError("Create Sale", "This action is restricted to staff.");
            return;
        }

        Long storeId = UserSession.getInstance().getStoreId();
        if (storeId == null || storeId <= 0) {
            showError("Create Sale", "Store is not set for this account.");
            return;
        }

        if (cartItems.isEmpty()) {
            showError("Create Sale", "Add at least one item.");
            return;
        }

        String payment = cbPaymentMethod.getSelectionModel().getSelectedItem();
        if (payment == null || payment.isBlank()) {
            showError("Create Sale", "Select a payment method.");
            return;
        }

        SaleRequest request = new SaleRequest();
        request.setStoreId(storeId);
        request.setPaymentMethod(payment);
        request.setIsRefund(false);

        List<SaleItemRequest> itemRequests = new ArrayList<>();
        for (SaleCartItem cartItem : cartItems) {
            SaleItemRequest itemRequest = new SaleItemRequest();
            itemRequest.setProdId((long) cartItem.getProdId());
            itemRequest.setQuantity(cartItem.getQuantity());
            itemRequests.add(itemRequest);
        }
        request.setItems(itemRequests);

        DropdownOption customerOption = cbCustomer.getValue();
        if (customerOption != null) {
            request.setCustomerId(customerOption.getId());
        }
        if (appliedCoupon != null) {
            request.setCouponId(appliedCoupon.getCouponId());
        }
        if (chkUseLoyaltyPoints.isSelected() && selectedCustomerData != null) {
            BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
            BigDecimal couponDiscount = calculateCouponDiscount(subtotal);
            BigDecimal subtotalAfterCoupon = subtotal.subtract(couponDiscount);
            request.setPointsUsed(calculatePointsToUse(subtotalAfterCoupon));
        }

        saleSaveInProgress = true;
        setCreateSaleControlsDisabled(true);
        btnRefund.setDisable(true);

        Task<SaleResponse> task = new Task<>() {
            @Override
            protected SaleResponse call() throws Exception {
                return SaleApi.getInstance().createSale(request);
            }
        };

        task.setOnSucceeded(evt -> {
            saleSaveInProgress = false;
            setCreateSaleControlsDisabled(false);
            btnRefund.setDisable(false);
            SaleResponse response = task.getValue();
            showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
            cartItems.clear();
            appliedCoupon = null;
            txtCouponCode.clear();
            txtCouponCode.setDisable(false);
            lblCouponStatus.setText("");
            hbCouponDiscount.setVisible(false);
            hbCouponDiscount.setManaged(false);
            hbLoyaltyDiscount.setVisible(false);
            hbLoyaltyDiscount.setManaged(false);
            hbPointsToEarn.setVisible(false);
            hbPointsToEarn.setManaged(false);
            cbCustomer.setValue(null);
            selectedCustomerData = null;
            lblLoyaltyPoints.setVisible(false);
            lblLoyaltyPoints.setManaged(false);
            chkUseLoyaltyPoints.setVisible(false);
            chkUseLoyaltyPoints.setManaged(false);
            chkUseLoyaltyPoints.setSelected(false);
            updateCartTotal();
            refreshSales(true);
        });

        task.setOnFailed(evt -> {
            saleSaveInProgress = false;
            setCreateSaleControlsDisabled(false);
            btnRefund.setDisable(false);
            Throwable e = task.getException();
            Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
            ActivityLogger.getInstance().logException("SaleController.btnSaveSale", ex, "Creating sale");
            String errorMsg = e != null ? e.getMessage() : null;
            if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
                showError("Create Sale", "Insufficient stock for one or more items.");
            } else {
                showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
            }
        });

        new Thread(task).start();
    }

    @FXML
    void btnRefund(ActionEvent event) {
        SaleLineItem selectedSale = tvSales.getSelectionModel().getSelectedItem();
        openRefundDialog(selectedSale != null ? (long) selectedSale.getSaleId() : null);
    }

    private void openRefundDialog(Long saleId) {
        try {
            SaleLineItem selectedSale = tvSales.getSelectionModel().getSelectedItem();
            if (selectedSale != null && selectedSale.isRefund()) {
                showError("Refund", "Select an original sale, not an existing refund.");
                return;
            }

            FXMLLoader loader = new FXMLLoader(getClass().getResource(
                "/org/example/petshopdesktop/dialogviews/refund-dialog-view.fxml"));
            Stage dialog = new Stage();
            dialog.initOwner(btnRefund.getScene().getWindow());
            dialog.initModality(Modality.APPLICATION_MODAL);
            dialog.setTitle("Process Refund");
            dialog.setScene(new Scene(loader.load()));
            var controller = loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController();
            if (saleId != null) {
                controller.prefillSale(saleId);
            }
            dialog.setMinWidth(860);
            dialog.setMinHeight(680);
            dialog.setResizable(true);
            dialog.showAndWait();

            refreshSales(true);
        } catch (Exception e) {
            ActivityLogger.getInstance().logException("SaleController.openRefundDialog", e, "Opening refund dialog");
        }
    }

    private void openSaleDetailDialog(int saleId) {
        Task<SaleResponse> task = new Task<>() {
            @Override
            protected SaleResponse call() throws Exception {
                return SaleApi.getInstance().getSale((long) saleId);
            }
        };

        task.setOnSucceeded(event -> {
            try {
                SaleResponse sale = task.getValue();
                FXMLLoader loader = new FXMLLoader(getClass().getResource(
                    "/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml"));
                Stage dialog = new Stage();
                dialog.initOwner(tvSales.getScene().getWindow());
                dialog.initModality(Modality.APPLICATION_MODAL);
                dialog.setTitle("Sale Details");
                dialog.setScene(new Scene(loader.load()));
                var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.SaleDetailDialogController) loader.getController();
                controller.setSaleId((long) sale.getSaleId());
                controller.setRefundAction(this::openRefundDialog);
                controller.displaySaleDetails(mapToSaleDetail(sale));
                dialog.setResizable(false);
                dialog.showAndWait();
            } catch (Exception e) {
                ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", e, "Opening sale detail dialog");
                showError("Sale Details", "Could not open the sale details.");
            }
        });

        task.setOnFailed(event -> {
            Throwable e = task.getException();
            ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", (Exception) e, "Loading sale detail");
            showError("Sale Details", "Could not open the sale details.");
        });

        new Thread(task).start();
    }

    private SaleDetail mapToSaleDetail(SaleResponse sale) {
        ObservableList<SaleDetail.SaleDetailItem> items = FXCollections.observableArrayList();
        double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
        double saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0;
        double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0;
        if (sale.getItems() != null) {
            boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
            for (SaleItemResponse item : sale.getItems()) {
                double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
                int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
                if (isRefund && quantity > 0) {
                    quantity = -quantity;
                }
                items.add(new SaleDetail.SaleDetailItem(
                    item.getProdId() != null ? item.getProdId().intValue() : 0,
                    item.getProductName(),
                    quantity,
                    unitPrice,
                    unitPrice * quantity * discountRatio
                ));
            }
        }
        double subtotal = sale.getSubtotalAmount() != null ? sale.getSubtotalAmount().doubleValue() : 0.0;
        double couponDiscount = sale.getCouponDiscountAmount() != null ? sale.getCouponDiscountAmount().doubleValue() : 0.0;
        double loyaltyDiscount = sale.getLoyaltyDiscountAmount() != null ? sale.getLoyaltyDiscountAmount().doubleValue() : 0.0;
        int pointsUsed = sale.getPointsUsed() != null ? sale.getPointsUsed() : 0;
        return new SaleDetail(
            sale.getSaleId().intValue(),
            sale.getSaleDate(),
            sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0,
            sale.getPaymentMethod(),
            sale.getEmployeeName(),
            Boolean.TRUE.equals(sale.getIsRefund()),
            items,
            sale.getCustomerName(),
            subtotal,
            couponDiscount,
            loyaltyDiscount,
            pointsUsed
        );
    }

    private void updateCartTotal() {
        BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
        BigDecimal couponDiscount = calculateCouponDiscount(subtotal);
        BigDecimal subtotalAfterCoupon = subtotal.subtract(couponDiscount);
        BigDecimal loyaltyDiscount = calculateLoyaltyDiscount(subtotalAfterCoupon);
        BigDecimal total = subtotalAfterCoupon.subtract(loyaltyDiscount);

        lblSubtotal.setText(currency.format(subtotal.doubleValue()));

        if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
            lblCouponDiscount.setText("-" + currency.format(couponDiscount.doubleValue()));
            hbCouponDiscount.setVisible(true);
            hbCouponDiscount.setManaged(true);
        } else {
            hbCouponDiscount.setVisible(false);
            hbCouponDiscount.setManaged(false);
        }

        if (loyaltyDiscount.compareTo(BigDecimal.ZERO) > 0) {
            int pointsToUse = calculatePointsToUse(subtotalAfterCoupon);
            lblLoyaltyDiscountLabel.setText("Loyalty Discount (" + pointsToUse + " pts):");
            lblLoyaltyDiscount.setText("-" + currency.format(loyaltyDiscount.doubleValue()));
            hbLoyaltyDiscount.setVisible(true);
            hbLoyaltyDiscount.setManaged(true);
        } else {
            hbLoyaltyDiscount.setVisible(false);
            hbLoyaltyDiscount.setManaged(false);
        }

        lblCartTotal.setText(currency.format(Math.max(0, total.doubleValue())));

        if (selectedCustomerData != null) {
            int pointsToEarn = (int) Math.max(0, total.doubleValue());
            lblPointsToEarn.setText("+" + pointsToEarn + " pts");
            hbPointsToEarn.setVisible(true);
            hbPointsToEarn.setManaged(true);
        } else {
            hbPointsToEarn.setVisible(false);
            hbPointsToEarn.setManaged(false);
        }
    }

    private BigDecimal calculateCouponDiscount(BigDecimal subtotal) {
        if (appliedCoupon == null) {
            return BigDecimal.ZERO;
        }
        if ("PERCENTAGE".equalsIgnoreCase(appliedCoupon.getDiscountType())) {
            return subtotal.multiply(appliedCoupon.getDiscountValue()).divide(BigDecimal.valueOf(100));
        } else {
            return appliedCoupon.getDiscountValue().min(subtotal);
        }
    }

    private BigDecimal calculateLoyaltyDiscount(BigDecimal subtotalAfterCoupon) {
        if (!chkUseLoyaltyPoints.isSelected() || selectedCustomerData == null) {
            return BigDecimal.ZERO;
        }
        int pointsToUse = calculatePointsToUse(subtotalAfterCoupon);
        return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05));
    }

    private int calculatePointsToUse(BigDecimal subtotalAfterCoupon) {
        if (selectedCustomerData == null || selectedCustomerData.getLoyaltyPoints() == null) {
            return 0;
        }
        int maxPointsNeeded = subtotalAfterCoupon.multiply(BigDecimal.valueOf(20)).intValue();
        return Math.min(selectedCustomerData.getLoyaltyPoints(), maxPointsNeeded);
    }

    private void updateSalesColumnWidths(double tableWidth) {
        double available = Math.max(tableWidth - 28.0, 0.0);
        double baseWidth = 1395.0;
        if (available <= 0) {
            return;
        }

        if (available <= baseWidth) {
            colSaleId.setPrefWidth(60.0);
            colSaleDate.setPrefWidth(170.0);
            colEmployeeName.setPrefWidth(160.0);
            colCustomerName.setPrefWidth(140.0);
            colServiceProduct.setPrefWidth(320.0);
            colSaleQuantity.setPrefWidth(70.0);
            colSaleUnitPrice.setPrefWidth(115.0);
            colSaleTotal.setPrefWidth(120.0);
            colSalePaymentType.setPrefWidth(110.0);
            colStoreName.setPrefWidth(130.0);
            return;
        }

        double extra = available - baseWidth;
        colSaleId.setPrefWidth(60.0);
        colSaleDate.setPrefWidth(170.0 + extra * 0.14);
        colEmployeeName.setPrefWidth(160.0 + extra * 0.14);
        colCustomerName.setPrefWidth(140.0 + extra * 0.12);
        colServiceProduct.setPrefWidth(320.0 + extra * 0.34);
        colSaleQuantity.setPrefWidth(70.0);
        colSaleUnitPrice.setPrefWidth(115.0 + extra * 0.08);
        colSaleTotal.setPrefWidth(120.0 + extra * 0.08);
        colSalePaymentType.setPrefWidth(110.0 + extra * 0.06);
        colStoreName.setPrefWidth(130.0 + extra * 0.04);
    }

    private void updateCartColumnWidths(double tableWidth) {
        double available = Math.max(tableWidth - 28.0, 0.0);
        double baseWidth = 640.0;
        if (available <= 0) {
            return;
        }

        if (available <= baseWidth) {
            colCartProduct.setPrefWidth(310.0);
            colCartQty.setPrefWidth(90.0);
            colCartUnitPrice.setPrefWidth(120.0);
            colCartTotal.setPrefWidth(120.0);
            return;
        }

        double extra = available - baseWidth;
        colCartProduct.setPrefWidth(310.0 + extra * 0.55);
        colCartQty.setPrefWidth(90.0);
        colCartUnitPrice.setPrefWidth(120.0 + extra * 0.2);
        colCartTotal.setPrefWidth(120.0 + extra * 0.25);
    }

    private void setCreateSaleControlsDisabled(boolean disabled) {
        cbProduct.setDisable(disabled);
        spQuantity.setDisable(disabled);
        btnAddToCart.setDisable(disabled);
        btnRemoveSelected.setDisable(disabled);
        cbPaymentMethod.setDisable(disabled);
        btnClearCart.setDisable(disabled);
        btnSaveSale.setDisable(disabled);
        cbCustomer.setDisable(disabled);
        txtCouponCode.setDisable(disabled);
        btnApplyCoupon.setDisable(disabled);
        btnClearCoupon.setDisable(disabled);
        chkUseLoyaltyPoints.setDisable(disabled);
    }

    private void applySalesFilter(String filter) {
        String f = filter == null ? "" : filter.trim().toLowerCase();
        String selectedPayment = cbFilterPayment.getValue();
        String selectedStatus = cbFilterRefundStatus.getValue();
        DropdownOption selectedCustomer = cbFilterCustomer.getValue();

        filteredSales.setPredicate(s -> {
            boolean textMatch = f.isEmpty()
                || String.valueOf(s.getSaleId()).contains(f)
                || safe(s.getSaleDate()).contains(f)
                || safe(s.getEmployeeName()).contains(f)
                || safe(s.getCustomerName()).contains(f)
                || safe(s.getItemName()).contains(f)
                || safe(s.getPaymentMethod()).contains(f);

            boolean paymentMatch = selectedPayment == null || selectedPayment.equals("All Payments")
                || safe(s.getPaymentMethod()).equals(selectedPayment.toLowerCase());

            boolean refundMatch = selectedStatus == null || selectedStatus.equals("All Status")
                || (selectedStatus.equals("Sales Only") && !s.isRefund())
                || (selectedStatus.equals("Refunds Only") && s.isRefund());

            boolean customerMatch = selectedCustomer == null || selectedCustomer.getId() == null
                || safe(s.getCustomerName()).equals(safe(selectedCustomer.getLabel()));

            return textMatch && paymentMatch && refundMatch && customerMatch;
        });
    }

    private static String safe(String v) {
        return v == null ? "" : v.toLowerCase();
    }

    private void loadCustomerFilter() {
        new Thread(() -> {
            try {
                List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
                DropdownOption allCustomers = new DropdownOption();
                allCustomers.setLabel("All Customers");
                Platform.runLater(() -> {
                    java.util.List<DropdownOption> items = new java.util.ArrayList<>();
                    items.add(allCustomers);
                    items.addAll(customers);
                    cbFilterCustomer.setItems(FXCollections.observableArrayList(items));
                    cbFilterCustomer.getSelectionModel().selectFirst();
                });
            } catch (Exception e) {
                ActivityLogger.getInstance().logException("SaleController.loadCustomerFilter", e, "Loading customer filter");
            }
        }).start();
    }

    private void loadStoreFilter() {
        new Thread(() -> {
            try {
                List<DropdownOption> stores = DropdownApi.getInstance().getStores();
                DropdownOption allStores = new DropdownOption();
                allStores.setLabel("All Stores");
                Platform.runLater(() -> {
                    storeOptions.clear();
                    storeOptions.addAll(stores);
                    java.util.List<DropdownOption> items = new java.util.ArrayList<>();
                    items.add(allStores);
                    items.addAll(stores);
                    cbStoreFilter.setItems(FXCollections.observableArrayList(items));
                    cbStoreFilter.getSelectionModel().selectFirst();
                });
            } catch (Exception e) {
                ActivityLogger.getInstance().logException("SaleController.loadStoreFilter", e, "Loading store filter");
            }
        }).start();
    }

    private Long selectedStoreId() {
        if (!UserSession.getInstance().isAdmin()) return UserSession.getInstance().getStoreId();
        DropdownOption selected = cbStoreFilter.getValue();
        return (selected != null && selected.getId() != null) ? selected.getId() : null;
    }

    private void showError(String title, String message) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle(title);
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }

    private void showInfo(String title, String message) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle(title);
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }
}
