package org.example.petshopdesktop.controllers;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
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.AnalyticsApi;
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;

    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();
        if (tabPane != null) {
            tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
                if (oldTab != newTab && newTab != null) {
                    loadAnalyticsData();
                }
            });
        }
        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);
        if (lblStatus != null) {
            lblStatus.setVisible(false);
        }
        new Thread(() -> {
            try {
                DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);

                Platform.runLater(() -> {
                    try {
                        boolean isAdmin = UserSession.getInstance().isAdmin();
                        loadSummaryData(dashboard);
                        loadSalesOverTime(dashboard);
                        loadTopProductsByRevenue(dashboard);
                        loadTopProductsByQuantity(dashboard);
                        loadPaymentMethodDistribution(dashboard);
                        loadEmployeePerformance(dashboard, isAdmin);
                        applyRoleVisibility(isAdmin);
                    } catch (Exception e) {
                        ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
                        lblError.setText("Error loading analytics data. Please try again.");
                        lblError.setVisible(true);
                    }
                });
            } catch (Exception e) {
                if (UserSession.getInstance().isStaff()) {
                    try {
                        DashboardResponse fallback = buildStaffFallbackDashboard();
                        Platform.runLater(() -> {
                            try {
                                loadSummaryData(fallback);
                                loadSalesOverTime(fallback);
                                loadTopProductsByRevenue(fallback);
                                loadTopProductsByQuantity(fallback);
                                loadPaymentMethodDistribution(fallback);
                                loadEmployeePerformance(fallback, false);
                                applyRoleVisibility(false);
                                lblError.setVisible(false);
                            } catch (Exception inner) {
                                ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", inner, "Rendering fallback analytics data");
                                lblError.setText("Error loading analytics data. Please try again.");
                                lblError.setVisible(true);
                            }
                        });
                        return;
                    } catch (Exception fallbackError) {
                        ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", fallbackError, "Loading fallback analytics data");
                    }
                }

                Platform.runLater(() -> {
                    ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
                    lblError.setText("Error loading analytics data. Please try again.");
                    lblError.setVisible(true);
                });
            }
        }).start();
    }

    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) {
        chartEmployeePerformance.setVisible(isAdmin);
        chartEmployeePerformance.setManaged(isAdmin);
        if (chartEmployeePerformance.getParent() != null) {
            chartEmployeePerformance.getParent().setVisible(isAdmin);
            chartEmployeePerformance.getParent().setManaged(isAdmin);
        }
    }

    private DashboardResponse buildStaffFallbackDashboard() throws Exception {
        List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
        String employeeName = UserSession.getInstance().getEmployeeName();
        if (employeeName == null || employeeName.isBlank()) {
            employeeName = UserSession.getInstance().getUsername();
        }
        final String employeeNameFilter = employeeName;

        List<SaleResponse> personalSales = sales.stream()
            .filter(sale -> sale.getEmployeeName() != null && sale.getEmployeeName().equalsIgnoreCase(employeeNameFilter))
            .toList();

        DashboardResponse dashboard = new DashboardResponse();
        dashboard.setSalesSummary(buildSalesSummary(personalSales));
        dashboard.setDailySales(buildDailySales(personalSales, 30));
        dashboard.setTopProducts(buildTopProducts(personalSales, 10));
        dashboard.setPaymentMethods(buildPaymentMethods(personalSales));
        dashboard.setEmployeePerformance(List.of(new DashboardResponse.EmployeePerformanceData()));
        return dashboard;
    }

    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> buildDailySales(List<SaleResponse> sales, int days) {
        Map<LocalDate, DailySales> daily = new LinkedHashMap<>();
        LocalDate start = LocalDate.now().minusDays(days - 1L);
        for (int i = 0; i < days; i++) {
            LocalDate date = start.plusDays(i);
            DailySales row = new DailySales();
            row.setDate(date.toString());
            row.setRevenue(BigDecimal.ZERO);
            row.setSalesCount(0L);
            daily.put(date, row);
        }
        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");
    }
}
