package org.example.petshopdesktop.controllers;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.*;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.example.petshopdesktop.api.dto.analytics.DailySales;
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TableViewSupport;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import javafx.util.StringConverter;

import java.util.*;
import java.util.stream.Collectors;

public class AnalyticsController {

    @FXML
    private Button btnRefresh;

    @FXML
    private Label lblStatus;

    @FXML
    private Label lblError;

    @FXML
    private Label lblTotalRevenue;

    @FXML
    private Label lblTotalTransactions;

    @FXML
    private Label lblAvgTransaction;

    @FXML
    private Label lblTotalItems;

    @FXML
    private LineChart<Number, Number> chartSalesOverTime;

    @FXML
    private NumberAxis axisSalesDate;

    @FXML
    private BarChart<Number, String> chartTopRevenue;

    @FXML
    private BarChart<Number, String> chartTopQuantity;

    @FXML
    private PieChart chartPaymentMethods;

    @FXML
    private BarChart<String, Number> chartEmployeePerformance;

    @FXML
    private TabPane tabPane;

    @FXML
    private Button btnToggleFilters;

    @FXML
    private Label lblFilterSummary;

    @FXML
    private VBox vbFilterContent;

    @FXML
    private DatePicker dpStartDate;

    @FXML
    private DatePicker dpEndDate;

    @FXML
    private ComboBox<String> cbPaymentFilter;

    @FXML
    private ComboBox<String> cbTopN;

    @FXML
    private ComboBox<String> cbStoreFilter;

    @FXML
    private HBox hbViewToggle;

    @FXML
    private ToggleButton tbnMyAnalytics;

    @FXML
    private ToggleButton tbnStoreAnalytics;

    private String viewMode = "store";

    private List<SaleResponse> cachedSales = new ArrayList<>();
    private FilterState currentFilter = new FilterState();

    private static final int[] TOP_N_VALUES = {5, 10, 15, 20};

    private static final String SALES_COLOR = "#ff6b35";
    private static final String REVENUE_COLOR = "#4ecdc4";
    private static final String QUANTITY_COLOR = "#ff9f1c";
    private static final String EMPLOYEE_COLOR = "#1a759f";
    private static final List<String> PIE_COLORS = List.of("#ff6b35", "#4ecdc4", "#1a759f", "#ff9f1c", "#577590", "#90be6d");

    private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
    private final NumberFormat wholeNumber = NumberFormat.getIntegerInstance();
    private final DateTimeFormatter chartDateFormatter = DateTimeFormatter.ofPattern("MMM d", Locale.CANADA);
    private final List<String> salesDateLabels = new ArrayList<>();

    @FXML
    public void initialize() {
        configureCharts();

        cbTopN.setItems(FXCollections.observableArrayList("Top 5", "Top 10", "Top 15", "Top 20"));
        cbTopN.getSelectionModel().selectFirst();

        cbPaymentFilter.setItems(FXCollections.observableArrayList("All"));
        cbPaymentFilter.getSelectionModel().selectFirst();

        cbStoreFilter.setItems(FXCollections.observableArrayList("All Stores"));
        cbStoreFilter.getSelectionModel().selectFirst();

        lblFilterSummary.setText("All time");

        ToggleGroup tgViewMode = new ToggleGroup();
        tbnMyAnalytics.setToggleGroup(tgViewMode);
        tbnStoreAnalytics.setToggleGroup(tgViewMode);
        tbnStoreAnalytics.setSelected(true);
        tgViewMode.selectedToggleProperty().addListener((obs, oldVal, newVal) -> {
            if (newVal == null) {
                (viewMode.equals("mine") ? tbnMyAnalytics : tbnStoreAnalytics).setSelected(true);
                return;
            }
            viewMode = (newVal == tbnMyAnalytics) ? "mine" : "store";
            updateViewModeStyles();
            updateStoreFilterVisibility();
            applyCurrentFilter();
        });

        hbViewToggle.setVisible(true);
        hbViewToggle.setManaged(true);

        loadAnalyticsData();
    }

    private void disableAllCharts() {
        chartSalesOverTime.setVisible(false);
        chartTopRevenue.setVisible(false);
        chartTopQuantity.setVisible(false);
        chartPaymentMethods.setVisible(false);
        chartEmployeePerformance.setVisible(false);
        btnRefresh.setDisable(true);
    }

    private void configureCharts() {
        chartSalesOverTime.setAnimated(false);
        axisSalesDate.setAnimated(false);
        axisSalesDate.setAutoRanging(false);
        axisSalesDate.setForceZeroInRange(false);
        axisSalesDate.setMinorTickVisible(false);
        axisSalesDate.setTickUnit(1);
        axisSalesDate.setTickLabelRotation(-45);
        axisSalesDate.setTickLabelFormatter(new StringConverter<>() {
            @Override
            public String toString(Number value) {
                int index = value.intValue();
                if (index < 0 || index >= salesDateLabels.size()) {
                    return "";
                }
                int labelStep = Math.max(1, (salesDateLabels.size() + 14) / 15);
                int lastIndex = salesDateLabels.size() - 1;
                boolean nearEnd = index >= Math.max(0, lastIndex - 2);
                boolean showLabel = index == 0 || nearEnd || index % labelStep == 0;
                return showLabel ? salesDateLabels.get(index) : "";
            }

            @Override
            public Number fromString(String string) {
                return 0;
            }
        });
        chartTopRevenue.setAnimated(true);
        chartTopQuantity.setAnimated(true);
        chartPaymentMethods.setAnimated(true);
        chartEmployeePerformance.setAnimated(true);
    }

    private void loadAnalyticsData() {
        lblError.setVisible(false);
        lblStatus.setVisible(false);
        btnRefresh.setDisable(true);
        new Thread(() -> {
            try {
                Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId();
                List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null, storeId);
                Platform.runLater(() -> {
                    cachedSales = sales;
                    derivePaymentMethods();
                    deriveStores();
                    updateStoreFilterVisibility();
                    applyCurrentFilter();
                    btnRefresh.setDisable(false);
                });
            } catch (Exception e) {
                Platform.runLater(() -> {
                    ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading sales data for analytics");
                    lblError.setText("Error loading analytics data. Please try again.");
                    lblError.setVisible(true);
                    btnRefresh.setDisable(false);
                });
            }
        }).start();
    }

    private void updateViewModeStyles() {
        String selectedStyle = "-fx-background-color: #4ECDC4; -fx-text-fill: white; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;";
        String unselectedStyle = "-fx-background-color: #e2e8f0; -fx-text-fill: #475569; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;";
        if (viewMode.equals("mine")) {
            tbnMyAnalytics.setStyle(selectedStyle + " -fx-background-radius: 6 0 0 6;");
            tbnStoreAnalytics.setStyle(unselectedStyle + " -fx-background-radius: 0 6 6 0;");
        } else {
            tbnMyAnalytics.setStyle(unselectedStyle + " -fx-background-radius: 6 0 0 6;");
            tbnStoreAnalytics.setStyle(selectedStyle + " -fx-background-radius: 0 6 6 0;");
        }
    }

    private void applyCurrentFilter() {
        try {
            List<SaleResponse> salesForMode;
            if (viewMode.equals("mine")) {
                String myName = UserSession.getInstance().getEmployeeName();
                salesForMode = cachedSales.stream()
                    .filter(s -> myName != null && myName.equalsIgnoreCase(s.getEmployeeName() != null ? s.getEmployeeName() : ""))
                    .collect(Collectors.toList());
            } else {
                salesForMode = cachedSales;
            }
            String storeFilter = currentFilter.storeFilter;
            if (!storeFilter.equals("All Stores") && !storeFilter.isBlank()) {
                salesForMode = salesForMode.stream()
                    .filter(s -> storeFilter.equalsIgnoreCase(s.getStoreName() != null ? s.getStoreName() : ""))
                    .collect(Collectors.toList());
            }
            List<SaleResponse> filtered = filterSales(salesForMode, currentFilter);
            String start = currentFilter.startDate.isEmpty() ? LocalDate.now().minusDays(6).toString() : currentFilter.startDate;
            String end = currentFilter.endDate.isEmpty() ? LocalDate.now().toString() : currentFilter.endDate;

            DashboardResponse dashboard = new DashboardResponse();
            dashboard.setSalesSummary(buildSalesSummary(filtered));
            dashboard.setDailySales(buildDailySalesBetween(filtered, start, end));
            dashboard.setTopProducts(buildTopProducts(filtered, currentFilter.topN > 0 ? currentFilter.topN : 5));
            dashboard.setPaymentMethods(buildPaymentMethods(filtered));
            dashboard.setEmployeePerformance(buildEmployeePerformance(filtered));

            boolean isAdmin = UserSession.getInstance().isAdmin();
            loadSummaryData(dashboard);
            loadSalesOverTime(dashboard);
            loadTopProductsByRevenue(dashboard);
            loadTopProductsByQuantity(dashboard);
            loadPaymentMethodDistribution(dashboard);
            loadEmployeePerformance(dashboard, isAdmin);
            applyRoleVisibility(isAdmin);
            updateFilterSummary();
        } catch (Exception e) {
            ActivityLogger.getInstance().logException("AnalyticsController.applyCurrentFilter", e, "Computing analytics from filtered sales");
            lblError.setText("Error computing analytics. Please try again.");
            lblError.setVisible(true);
        }
    }

    private List<SaleResponse> filterSales(List<SaleResponse> sales, FilterState filter) {
        return sales.stream().filter(sale -> {
            String date = sale.getSaleDate() != null ? sale.getSaleDate().toLocalDate().toString() : "";
            if (!filter.startDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.startDate) < 0) return false;
            if (!filter.endDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.endDate) > 0) return false;
            if (!filter.paymentMethod.equals("All") && !filter.paymentMethod.isEmpty()) {
                if (!filter.paymentMethod.equalsIgnoreCase(sale.getPaymentMethod())) return false;
            }
            return true;
        }).collect(Collectors.toList());
    }

    private void derivePaymentMethods() {
        Set<String> methods = new TreeSet<>();
        for (SaleResponse s : cachedSales) {
            if (s.getPaymentMethod() != null && !s.getPaymentMethod().isEmpty()) {
                methods.add(s.getPaymentMethod());
            }
        }
        List<String> items = new ArrayList<>();
        items.add("All");
        items.addAll(methods);
        String current = cbPaymentFilter.getValue();
        cbPaymentFilter.setItems(FXCollections.observableArrayList(items));
        if (current != null && items.contains(current)) {
            cbPaymentFilter.setValue(current);
        } else {
            cbPaymentFilter.getSelectionModel().selectFirst();
        }
    }

    private void deriveStores() {
        Set<String> stores = new TreeSet<>();
        for (SaleResponse s : cachedSales) {
            if (s.getStoreName() != null && !s.getStoreName().isBlank()) {
                stores.add(s.getStoreName());
            }
        }
        List<String> items = new ArrayList<>();
        items.add("All Stores");
        items.addAll(stores);
        String current = cbStoreFilter.getValue();
        cbStoreFilter.setItems(FXCollections.observableArrayList(items));
        if (current != null && items.contains(current)) {
            cbStoreFilter.setValue(current);
        } else {
            cbStoreFilter.getSelectionModel().selectFirst();
        }
    }

    private void updateStoreFilterVisibility() {
        boolean show = UserSession.getInstance().isAdmin() && viewMode.equals("store");
        cbStoreFilter.setVisible(show);
        cbStoreFilter.setManaged(show);
    }

    private void updateFilterSummary() {
        String start = currentFilter.startDate;
        String end = currentFilter.endDate;
        if (start.isEmpty() && end.isEmpty()) {
            lblFilterSummary.setText("All time");
        } else if (start.isEmpty()) {
            lblFilterSummary.setText("Up to " + shortDate(end));
        } else if (end.isEmpty()) {
            lblFilterSummary.setText("From " + shortDate(start));
        } else {
            lblFilterSummary.setText(shortDate(start) + " – " + shortDate(end));
        }
    }

    private String shortDate(String date) {
        return (date != null && date.length() >= 10) ? date.substring(5) : date;
    }

    private void loadSummaryData(DashboardResponse dashboard) throws Exception {
        if (dashboard != null) {
            BigDecimal totalRevenue = BigDecimal.ZERO;
            Long totalSales = 0L;
            if (dashboard.getSalesSummary() != null) {
                totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
                totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
                lblTotalItems.setText(wholeNumber.format(dashboard.getSalesSummary().getTotalItemsSold() != null ? dashboard.getSalesSummary().getTotalItemsSold() : 0L));
            } else {
                lblTotalItems.setText(wholeNumber.format(0));
            }

            lblTotalRevenue.setText(currency.format(totalRevenue));
            lblTotalTransactions.setText(wholeNumber.format(totalSales));

            BigDecimal avgTransaction = BigDecimal.ZERO;
            if (totalSales > 0) {
                avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
            }
            lblAvgTransaction.setText(currency.format(avgTransaction));
        }
    }

    private void loadSalesOverTime(DashboardResponse dashboard) throws Exception {
        List<DailySales> dailySales = dashboard.getDailySales() != null ? dashboard.getDailySales() : new ArrayList<>();
        XYChart.Series<Number, Number> series = new XYChart.Series<>();
        series.setName("Daily Revenue");

        salesDateLabels.clear();
        for (int i = 0; i < dailySales.size(); i++) {
            DailySales dailySale = dailySales.get(i);
            salesDateLabels.add(formatChartDate(dailySale.getDate()));
            BigDecimal revenue = dailySale.getRevenue() != null ? dailySale.getRevenue() : BigDecimal.ZERO;
            series.getData().add(new XYChart.Data<>(i, revenue));
        }

        int upperBound = Math.max(0, salesDateLabels.size() - 1);
        axisSalesDate.setLowerBound(0);
        axisSalesDate.setUpperBound(upperBound);
        chartSalesOverTime.getData().clear();
        chartSalesOverTime.getData().add(series);
        applyLineChartColor(chartSalesOverTime, SALES_COLOR);
    }

    private String formatChartDate(String date) {
        if (date == null || date.isBlank()) {
            return "";
        }

        try {
            return LocalDate.parse(date).format(chartDateFormatter);
        } catch (DateTimeParseException ignored) {
            return date;
        }
    }

    private void loadTopProductsByRevenue(DashboardResponse dashboard) throws Exception {
        List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
        XYChart.Series<Number, String> series = new XYChart.Series<>();
        series.setName("Revenue");

        for (TopProduct product : topProducts) {
            BigDecimal revenue = product.getRevenue() != null ? product.getRevenue() : BigDecimal.ZERO;
            series.getData().add(new XYChart.Data<>(revenue, product.getProductName()));
        }

        chartTopRevenue.getData().clear();
        chartTopRevenue.getData().add(series);
        applyBarChartColor(chartTopRevenue, REVENUE_COLOR);
    }

    private void loadTopProductsByQuantity(DashboardResponse dashboard) throws Exception {
        List<TopProduct> topProducts = dashboard.getTopProducts() != null ? dashboard.getTopProducts() : new ArrayList<>();
        XYChart.Series<Number, String> series = new XYChart.Series<>();
        series.setName("Quantity");

        for (TopProduct product : topProducts) {
            Long quantitySold = product.getQuantitySold() != null ? product.getQuantitySold() : 0L;
            series.getData().add(new XYChart.Data<>(quantitySold, product.getProductName()));
        }

        chartTopQuantity.getData().clear();
        chartTopQuantity.getData().add(series);
        applyBarChartColor(chartTopQuantity, QUANTITY_COLOR);
    }

    private void loadPaymentMethodDistribution(DashboardResponse dashboard) throws Exception {
        chartPaymentMethods.getData().clear();

        List<DashboardResponse.PaymentMethodData> paymentEntries = dashboard.getPaymentMethods() != null ? dashboard.getPaymentMethods() : List.of();
        for (DashboardResponse.PaymentMethodData entry : paymentEntries) {
            PieChart.Data slice = new PieChart.Data(
                entry.getPaymentMethod() + " (" + entry.getCount() + ")",
                entry.getCount()
            );
            chartPaymentMethods.getData().add(slice);
        }

        chartPaymentMethods.setLabelsVisible(false);
        applyPieChartColors();
    }

    private void loadEmployeePerformance(DashboardResponse dashboard, boolean isAdmin) throws Exception {
        XYChart.Series<String, Number> series = new XYChart.Series<>();
        series.setName(isAdmin ? "Revenue" : "My Revenue");

        List<DashboardResponse.EmployeePerformanceData> employeeEntries = dashboard.getEmployeePerformance() != null ? dashboard.getEmployeePerformance() : List.of();
        for (DashboardResponse.EmployeePerformanceData entry : employeeEntries) {
            BigDecimal revenue = entry.getRevenue() != null ? entry.getRevenue() : BigDecimal.ZERO;
            series.getData().add(new XYChart.Data<>(entry.getEmployeeName(), revenue));
        }

        chartEmployeePerformance.getData().clear();
        chartEmployeePerformance.getData().add(series);
        applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR);
    }

    private void applyRoleVisibility(boolean isAdmin) {
        boolean showEmpChart = isAdmin && viewMode.equals("store");
        chartEmployeePerformance.setVisible(showEmpChart);
        chartEmployeePerformance.setManaged(showEmpChart);
        if (chartEmployeePerformance.getParent() != null) {
            chartEmployeePerformance.getParent().setVisible(showEmpChart);
            chartEmployeePerformance.getParent().setManaged(showEmpChart);
        }
    }

    private List<DashboardResponse.EmployeePerformanceData> buildEmployeePerformance(List<SaleResponse> sales) {
        Map<String, BigDecimal> empRevenue = new LinkedHashMap<>();
        for (SaleResponse sale : sales) {
            if (Boolean.TRUE.equals(sale.getIsRefund())) continue;
            String emp = sale.getEmployeeName() != null ? sale.getEmployeeName() : "Unknown";
            BigDecimal amount = sale.getTotalAmount() != null ? sale.getTotalAmount() : BigDecimal.ZERO;
            empRevenue.merge(emp, amount, BigDecimal::add);
        }
        return empRevenue.entrySet().stream()
            .sorted((a, b) -> b.getValue().compareTo(a.getValue()))
            .map(e -> {
                DashboardResponse.EmployeePerformanceData d = new DashboardResponse.EmployeePerformanceData();
                d.setEmployeeName(e.getKey());
                d.setRevenue(e.getValue());
                return d;
            })
            .collect(Collectors.toList());
    }

    private DashboardResponse.SalesSummary buildSalesSummary(List<SaleResponse> sales) {
        DashboardResponse.SalesSummary summary = new DashboardResponse.SalesSummary();
        BigDecimal totalRevenue = BigDecimal.ZERO;
        long totalSales = 0L;
        BigDecimal totalRefunds = BigDecimal.ZERO;
        long totalRefundCount = 0L;
        long totalItemsSold = 0L;

        for (SaleResponse sale : sales) {
            boolean refund = Boolean.TRUE.equals(sale.getIsRefund());
            BigDecimal amount = sale.getTotalAmount() != null ? sale.getTotalAmount() : BigDecimal.ZERO;
            if (refund) {
                totalRefunds = totalRefunds.add(amount);
                totalRefundCount++;
                continue;
            }
            totalRevenue = totalRevenue.add(amount);
            totalSales++;
            if (sale.getItems() != null) {
                totalItemsSold += sale.getItems().stream().mapToLong(item -> item.getQuantity() == null ? 0 : item.getQuantity()).sum();
            }
        }

        summary.setTotalRevenue(totalRevenue);
        summary.setTotalSales(totalSales);
        summary.setTotalRefunds(totalRefunds);
        summary.setTotalRefundCount(totalRefundCount);
        summary.setTotalItemsSold(totalItemsSold);
        return summary;
    }

    private List<DailySales> buildDailySalesBetween(List<SaleResponse> sales, String startStr, String endStr) {
        Map<LocalDate, DailySales> daily = new LinkedHashMap<>();
        try {
            LocalDate start = LocalDate.parse(startStr);
            LocalDate end = LocalDate.parse(endStr);
            if (start.plusDays(60).isBefore(end)) {
                start = end.minusDays(59);
            }
            for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
                DailySales row = new DailySales();
                row.setDate(date.toString());
                row.setRevenue(BigDecimal.ZERO);
                row.setSalesCount(0L);
                daily.put(date, row);
            }
        } catch (Exception e) {
            return new ArrayList<>();
        }
        for (SaleResponse sale : sales) {
            if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getSaleDate() == null) continue;
            LocalDate date = sale.getSaleDate().toLocalDate();
            DailySales row = daily.get(date);
            if (row != null) {
                row.setRevenue(row.getRevenue().add(sale.getTotalAmount() == null ? BigDecimal.ZERO : sale.getTotalAmount()));
                row.setSalesCount(row.getSalesCount() + 1);
            }
        }
        return new ArrayList<>(daily.values());
    }

    private List<TopProduct> buildTopProducts(List<SaleResponse> sales, int top) {
        Map<Long, TopProduct> totals = new HashMap<>();
        for (SaleResponse sale : sales) {
            if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getItems() == null) {
                continue;
            }
            for (SaleItemResponse item : sale.getItems()) {
                if (item.getProdId() == null) {
                    continue;
                }
                TopProduct product = totals.computeIfAbsent(item.getProdId(), id -> {
                    TopProduct p = new TopProduct();
                    p.setProductId(id);
                    p.setProductName(item.getProductName());
                    p.setQuantitySold(0L);
                    p.setRevenue(BigDecimal.ZERO);
                    return p;
                });
                long quantity = item.getQuantity() == null ? 0 : item.getQuantity();
                BigDecimal unitPrice = item.getUnitPrice() == null ? BigDecimal.ZERO : item.getUnitPrice();
                product.setQuantitySold(product.getQuantitySold() + quantity);
                product.setRevenue(product.getRevenue().add(unitPrice.multiply(BigDecimal.valueOf(quantity))));
            }
        }
        return totals.values().stream()
            .sorted((a, b) -> b.getRevenue().compareTo(a.getRevenue()))
            .limit(top)
            .toList();
    }

    private List<DashboardResponse.PaymentMethodData> buildPaymentMethods(List<SaleResponse> sales) {
        Map<String, Long> totals = new TreeMap<>();
        for (SaleResponse sale : sales) {
            if (Boolean.TRUE.equals(sale.getIsRefund())) {
                continue;
            }
            String method = sale.getPaymentMethod() == null || sale.getPaymentMethod().isBlank() ? "Unknown" : sale.getPaymentMethod();
            totals.merge(method, 1L, Long::sum);
        }
        return totals.entrySet().stream().map(entry -> {
            DashboardResponse.PaymentMethodData data = new DashboardResponse.PaymentMethodData();
            data.setPaymentMethod(entry.getKey());
            data.setCount(entry.getValue());
            return data;
        }).toList();
    }

    private void applyLineChartColor(LineChart<Number, Number> chart, String color) {
        Platform.runLater(() -> {
            for (XYChart.Series<Number, Number> series : chart.getData()) {
                if (series.getNode() != null) {
                    series.getNode().setStyle("-fx-stroke: " + color + ";");
                }
                for (XYChart.Data<Number, Number> data : series.getData()) {
                    if (data.getNode() != null) {
                        data.getNode().setStyle("-fx-background-color: white, " + color + ";");
                    }
                }
            }
        });
    }

    private <X, Y> void applyBarChartColor(XYChart<X, Y> chart, String color) {
        Platform.runLater(() -> {
            for (XYChart.Series<X, Y> series : chart.getData()) {
                for (XYChart.Data<X, Y> data : series.getData()) {
                    if (data.getNode() != null) {
                        data.getNode().setStyle("-fx-bar-fill: " + color + ";");
                    }
                }
            }
        });
    }

    private void applyPieChartColors() {
        Platform.runLater(() -> {
            for (int i = 0; i < chartPaymentMethods.getData().size(); i++) {
                PieChart.Data slice = chartPaymentMethods.getData().get(i);
                if (slice.getNode() != null) {
                    slice.getNode().setStyle("-fx-pie-color: " + PIE_COLORS.get(i % PIE_COLORS.size()) + ";");
                }
            }
        });
    }

    @FXML
    void handleRefresh(ActionEvent event) {
        loadAnalyticsData();
        TableViewSupport.flashStatus(lblStatus, "Refreshed");
    }

    @FXML
    void btnToggleFilters(ActionEvent event) {
        boolean expanded = vbFilterContent.isVisible();
        vbFilterContent.setVisible(!expanded);
        vbFilterContent.setManaged(!expanded);
        btnToggleFilters.setText(expanded ? "▼  Filters" : "▲  Filters");
    }

    @FXML
    void btnPresetToday(ActionEvent event) { applyPreset(0, 0); }

    @FXML
    void btnPreset7D(ActionEvent event) { applyPreset(-6, 0); }

    @FXML
    void btnPreset30D(ActionEvent event) { applyPreset(-29, 0); }

    @FXML
    void btnPreset3M(ActionEvent event) { applyPreset(-89, 0); }

    @FXML
    void btnPreset1Y(ActionEvent event) { applyPreset(-364, 0); }

    @FXML
    void btnPresetAll(ActionEvent event) {
        dpStartDate.setValue(null);
        dpEndDate.setValue(null);
        applyFiltersFromUI();
    }

    @FXML
    void btnApplyFilter(ActionEvent event) {
        applyFiltersFromUI();
    }

    @FXML
    void btnResetFilter(ActionEvent event) {
        dpStartDate.setValue(null);
        dpEndDate.setValue(null);
        cbPaymentFilter.getSelectionModel().selectFirst();
        cbTopN.getSelectionModel().selectFirst();
        cbStoreFilter.getSelectionModel().selectFirst();
        currentFilter = new FilterState();
        applyCurrentFilter();
    }

    private void applyPreset(int startOffset, int endOffset) {
        dpStartDate.setValue(LocalDate.now().plusDays(startOffset));
        dpEndDate.setValue(LocalDate.now().plusDays(endOffset));
        applyFiltersFromUI();
    }

    private void applyFiltersFromUI() {
        currentFilter = new FilterState();
        currentFilter.startDate = dpStartDate.getValue() != null ? dpStartDate.getValue().toString() : "";
        currentFilter.endDate = dpEndDate.getValue() != null ? dpEndDate.getValue().toString() : "";
        String pm = cbPaymentFilter.getValue();
        currentFilter.paymentMethod = pm != null ? pm : "All";
        int topNPos = cbTopN.getSelectionModel().getSelectedIndex();
        currentFilter.topN = (topNPos >= 0 && topNPos < TOP_N_VALUES.length) ? TOP_N_VALUES[topNPos] : 5;
        String sf = cbStoreFilter.getValue();
        currentFilter.storeFilter = (sf != null && !sf.isBlank()) ? sf : "All Stores";
        applyCurrentFilter();
    }

    private static class FilterState {
        String startDate = "";
        String endDate = "";
        String paymentMethod = "All";
        int topN = 5;
        String storeFilter = "All Stores";
    }
}
