package org.example.petshopdesktop.controllers.dialogcontrollers;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.concurrent.Task;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.application.Platform;
import javafx.stage.Stage;
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.endpoints.SaleApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class RefundDialogController {

    @FXML
    private TextField txtSaleId;

    @FXML
    private Button btnLoadSale;

    @FXML
    private Label lblSaleInfo;

    @FXML
    private TableView<SaleItemResponse> tvOriginalItems;

    @FXML
    private TableColumn<SaleItemResponse, String> colOriginalProduct;

    @FXML
    private TableColumn<SaleItemResponse, Integer> colOriginalQuantity;

    @FXML
    private TableColumn<SaleItemResponse, BigDecimal> colOriginalUnitPrice;

    @FXML
    private TableColumn<SaleItemResponse, BigDecimal> colOriginalTotal;

    @FXML
    private Button btnAddToRefund;

    @FXML
    private TableView<RefundItem> tvRefundItems;

    @FXML
    private TableColumn<RefundItem, String> colRefundProduct;

    @FXML
    private TableColumn<RefundItem, Integer> colRefundQuantity;

    @FXML
    private TableColumn<RefundItem, Double> colRefundUnitPrice;

    @FXML
    private TableColumn<RefundItem, Double> colRefundTotal;

    @FXML
    private Button btnRemoveFromRefund;

    @FXML
    private ComboBox<String> cbPaymentMethod;

    @FXML
    private Label lblRefundSubtotal;

    @FXML
    private HBox hbRefundCouponDiscount;

    @FXML
    private Label lblRefundCouponDiscount;

    @FXML
    private HBox hbRefundLoyaltyDiscount;

    @FXML
    private Label lblRefundLoyaltyDiscount;

    @FXML
    private Label lblRefundTotal;

    @FXML
    private Button btnProcessRefund;

    @FXML
    private Button btnCancel;

    private SaleResponse currentSale;
    private final List<SaleItemResponse> baseOriginalItems = new ArrayList<>();
    private final ObservableList<SaleItemResponse> originalItems = FXCollections.observableArrayList();
    private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
    private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
    private boolean refundInProgress;

    @FXML
    public void initialize() {
        setupTables();
        cbPaymentMethod.setItems(FXCollections.observableArrayList("Cash", "Card"));
        cbPaymentMethod.getSelectionModel().selectFirst();
        updateRefundTotal();
    }

    private void setupTables() {
        tvOriginalItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        colOriginalProduct.setCellValueFactory(cell -> new ReadOnlyStringWrapper(cell.getValue().getProductName()));
        colOriginalQuantity.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getQuantity()));
        colOriginalUnitPrice.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getUnitPrice()));
        colOriginalTotal.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getLineTotal()));
        tvOriginalItems.setItems(originalItems);
        tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);

        tvRefundItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        colRefundProduct.setCellValueFactory(cell -> new ReadOnlyStringWrapper(cell.getValue().getProductName()));
        colRefundQuantity.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getQuantity()));
        colRefundUnitPrice.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getUnitPrice()));
        colRefundTotal.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getTotal()));
        tvRefundItems.setItems(refundItems);
        tvRefundItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
    }

    @FXML
    void btnLoadSaleClicked(ActionEvent event) {
        loadSale();
    }

    public void prefillSale(Long saleId) {
        if (saleId == null) {
            return;
        }
        txtSaleId.setText(String.valueOf(saleId));
        Platform.runLater(this::loadSale);
    }

    private void loadSale() {
        String saleIdText = txtSaleId.getText().trim();
        if (saleIdText.isEmpty()) {
            clearLoadedSale();
            showError("Load Sale", "Enter a transaction ID.");
            return;
        }

        Long saleId;
        try {
            saleId = Long.parseLong(saleIdText);
        } catch (NumberFormatException e) {
            clearLoadedSale();
            showError("Load Sale", "Invalid transaction ID.");
            return;
        }

        setLoadingState(true, "Loading sale...");
        clearLoadedSale();

        Task<LoadedSaleData> task = new Task<>() {
            @Override
            protected LoadedSaleData call() throws Exception {
                List<SaleResponse> allSales = SaleApi.getInstance().listAllSales(null, null);
                SaleResponse sale = SaleApi.getInstance().getSale(saleId);
                if (Boolean.TRUE.equals(sale.getIsRefund())) {
                    throw new IllegalStateException("Select an original sale, not a refund record.");
                }
                List<SaleResponse> previousRefunds = allSales.stream()
                    .filter(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()))
                    .collect(Collectors.toList());
                List<SaleItemResponse> refundableItems = buildRefundableItems(sale, previousRefunds);
                if (refundableItems.isEmpty()) {
                    throw new IllegalStateException("This sale has no remaining refundable items.");
                }
                return new LoadedSaleData(sale, refundableItems);
            }
        };

        task.setOnSucceeded(event -> {
            LoadedSaleData loaded = task.getValue();
            currentSale = loaded.sale();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
            String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
                currentSale.getSaleDate().format(formatter),
                currentSale.getEmployeeName(),
                currency.format(currentSale.getTotalAmount()),
                currentSale.getPaymentMethod());
            lblSaleInfo.setText(saleInfo);
            baseOriginalItems.clear();
            baseOriginalItems.addAll(copySaleItems(loaded.refundableItems()));
            originalItems.setAll(copySaleItems(loaded.refundableItems()));
            cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
            refundItems.clear();
            updateOriginalItemAvailability();
            updateRefundTotal();
            setLoadingState(false, saleInfo);
        });

        task.setOnFailed(event -> {
            clearLoadedSale();
            Throwable e = task.getException();
            ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", (Exception) e, "Loading sale");
            setLoadingState(false, "");
            showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
        });

        new Thread(task).start();
    }

    @FXML
    void btnAddToRefundClicked(ActionEvent event) {
        if (currentSale == null) {
            showError("Add to Refund", "Load a sale first.");
            return;
        }

        SaleItemResponse selected = tvOriginalItems.getSelectionModel().getSelectedItem();
        if (selected == null) {
            showError("Add to Refund", "Select an item from the original sale.");
            return;
        }

        int alreadyRefunded = refundItems.stream()
            .filter(r -> r.getProdId() == selected.getProdId().intValue())
            .mapToInt(RefundItem::getQuantity)
            .sum();

        int available = selected.getQuantity() - alreadyRefunded;
        if (available <= 0) {
            showError("Add to Refund", "All items of this product are already in the refund list.");
            return;
        }

        TextInputDialog dialog = new TextInputDialog(String.valueOf(available));
        dialog.setTitle("Refund Quantity");
        dialog.setHeaderText("Product: " + selected.getProductName());
        dialog.setContentText("Enter quantity to refund (max " + available + "):");

        Optional<String> result = dialog.showAndWait();
        if (result.isPresent()) {
            try {
                int quantity = Integer.parseInt(result.get().trim());
                if (quantity <= 0) {
                    showError("Add to Refund", "Quantity must be at least 1.");
                    return;
                }
                if (quantity > available) {
                    showError("Add to Refund", "Cannot refund more than " + available + " items.");
                    return;
                }

                addOrMergeRefundItem(selected, quantity);
                updateOriginalItemAvailability();
                updateRefundTotal();

            } catch (NumberFormatException e) {
                showError("Add to Refund", "Invalid quantity.");
            }
        }
    }

    @FXML
    void btnRemoveFromRefundClicked(ActionEvent event) {
        RefundItem selected = tvRefundItems.getSelectionModel().getSelectedItem();
        if (selected != null) {
            refundItems.remove(selected);
            updateOriginalItemAvailability();
            updateRefundTotal();
        }
    }

    @FXML
    void btnProcessRefundClicked(ActionEvent event) {
        if (refundInProgress) {
            return;
        }
        if (currentSale == null) {
            showError("Process Refund", "Load a sale first.");
            return;
        }

        if (refundItems.isEmpty()) {
            showError("Process Refund", "Add at least one item to refund.");
            return;
        }

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

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

        Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
        confirm.setTitle("Confirm Refund");
        confirm.setHeaderText("Process refund for sale ID " + currentSale.getSaleId() + "?");
        confirm.setContentText("Refund amount: " + lblRefundTotal.getText());

        Optional<ButtonType> confirmResult = confirm.showAndWait();
        if (confirmResult.isEmpty() || confirmResult.get() != ButtonType.OK) {
            return;
        }

        SaleRequest request = new SaleRequest();
        request.setStoreId(storeId);
        request.setPaymentMethod(payment);
        request.setIsRefund(true);
        request.setOriginalSaleId(currentSale.getSaleId());

        List<SaleItemRequest> items = new ArrayList<>();
        for (RefundItem item : refundItems) {
            SaleItemRequest saleItem = new SaleItemRequest();
            saleItem.setProdId((long) item.getProdId());
            saleItem.setQuantity(-item.getQuantity());
            items.add(saleItem);
        }
        request.setItems(items);

        refundInProgress = true;
        setLoadingState(true, lblSaleInfo.getText());

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

        task.setOnSucceeded(evt -> {
            refundInProgress = false;
            setLoadingState(false, lblSaleInfo.getText());
            SaleResponse refundResponse = task.getValue();
            Alert success = new Alert(Alert.AlertType.INFORMATION);
            success.setTitle("Refund Processed");
            success.setHeaderText(null);
            success.setContentText("Refund ID " + refundResponse.getSaleId() + " was created successfully.");
            success.showAndWait();
            closeDialog();
        });

        task.setOnFailed(evt -> {
            refundInProgress = false;
            setLoadingState(false, lblSaleInfo.getText());
            Throwable e = task.getException();
            Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
            ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", ex, "Processing refund");
            showError("Process Refund", e != null && e.getMessage() != null ? e.getMessage() : "Could not process refund.");
        });

        new Thread(task).start();
    }

    @FXML
    void btnCancelClicked(ActionEvent event) {
        closeDialog();
    }

    private void clearLoadedSale() {
        currentSale = null;
        lblSaleInfo.setText("");
        baseOriginalItems.clear();
        originalItems.clear();
        refundItems.clear();
        updateRefundTotal();
    }

    private void setLoadingState(boolean loading, String message) {
        btnLoadSale.setDisable(loading);
        btnAddToRefund.setDisable(loading);
        btnRemoveFromRefund.setDisable(loading);
        btnProcessRefund.setDisable(loading);
        txtSaleId.setDisable(loading);
        cbPaymentMethod.setDisable(loading);
        if (loading) {
            lblSaleInfo.setText(message);
        }
    }

    private void addOrMergeRefundItem(SaleItemResponse selected, int quantity) {
        for (int i = 0; i < refundItems.size(); i++) {
            RefundItem existing = refundItems.get(i);
            if (existing.getProdId() == selected.getProdId().intValue()) {
                refundItems.set(i, new RefundItem(
                    existing.getProdId(),
                    existing.getProductName(),
                    existing.getQuantity() + quantity,
                    existing.getUnitPrice()
                ));
                return;
            }
        }

        refundItems.add(new RefundItem(
            selected.getProdId().intValue(),
            selected.getProductName(),
            quantity,
            selected.getUnitPrice().doubleValue()
        ));
    }

    private void updateOriginalItemAvailability() {
        if (currentSale == null) {
            baseOriginalItems.clear();
            originalItems.clear();
            return;
        }

        Map<Long, Integer> pendingRefunds = new HashMap<>();
        for (RefundItem refundItem : refundItems) {
            pendingRefunds.merge((long) refundItem.getProdId(), refundItem.getQuantity(), Integer::sum);
        }

        List<SaleItemResponse> refreshedItems = new ArrayList<>();
        for (SaleItemResponse originalItem : baseOriginalItems) {
            SaleItemResponse refreshedItem = copySaleItem(originalItem);
            int pending = pendingRefunds.getOrDefault(refreshedItem.getProdId(), 0);
            refreshedItem.setQuantity(Math.max(0, refreshedItem.getQuantity() - pending));
            if (refreshedItem.getQuantity() > 0) {
                refreshedItems.add(refreshedItem);
            }
        }

        originalItems.setAll(refreshedItems);
        tvOriginalItems.getSelectionModel().clearSelection();
        tvOriginalItems.refresh();
        tvRefundItems.refresh();
    }

    private List<SaleItemResponse> copySaleItems(List<SaleItemResponse> items) {
        List<SaleItemResponse> copies = new ArrayList<>();
        for (SaleItemResponse item : items) {
            copies.add(copySaleItem(item));
        }
        return copies;
    }

    private SaleItemResponse copySaleItem(SaleItemResponse source) {
        SaleItemResponse copy = new SaleItemResponse();
        copy.setSaleItemId(source.getSaleItemId());
        copy.setProdId(source.getProdId());
        copy.setProductName(source.getProductName());
        copy.setQuantity(source.getQuantity());
        copy.setUnitPrice(source.getUnitPrice());
        return copy;
    }

    private void updateRefundTotal() {
        if (currentSale == null || refundItems.isEmpty()) {
            lblRefundSubtotal.setText(currency.format(0.0));
            lblRefundTotal.setText(currency.format(0.0));
            hbRefundCouponDiscount.setVisible(false);
            hbRefundCouponDiscount.setManaged(false);
            hbRefundLoyaltyDiscount.setVisible(false);
            hbRefundLoyaltyDiscount.setManaged(false);
            return;
        }

        double originalSubtotal = currentSale.getSubtotalAmount() != null
                ? currentSale.getSubtotalAmount().doubleValue() : 0;
        if (originalSubtotal == 0 && currentSale.getItems() != null) {
            for (var item : currentSale.getItems()) {
                if (item.getUnitPrice() != null && item.getQuantity() != null) {
                    originalSubtotal += item.getUnitPrice().doubleValue() * Math.abs(item.getQuantity());
                }
            }
        }

        double cartSubtotal = refundItems.stream().mapToDouble(RefundItem::getTotal).sum();
        double originalTotal = currentSale.getTotalAmount() != null
                ? Math.abs(currentSale.getTotalAmount().doubleValue()) : 0;

        double refundTotal;
        double couponDiscountRefunded = 0;
        double loyaltyDiscountRefunded = 0;

        if (originalSubtotal > 0) {
            double ratio = cartSubtotal / originalSubtotal;
            refundTotal = originalTotal * ratio;
            if (currentSale.getCouponDiscountAmount() != null) {
                couponDiscountRefunded = currentSale.getCouponDiscountAmount().doubleValue() * ratio;
            }
            if (currentSale.getLoyaltyDiscountAmount() != null) {
                loyaltyDiscountRefunded = currentSale.getLoyaltyDiscountAmount().doubleValue() * ratio;
            }
        } else {
            refundTotal = cartSubtotal;
        }

        lblRefundSubtotal.setText(currency.format(cartSubtotal));

        if (couponDiscountRefunded > 0.001) {
            lblRefundCouponDiscount.setText("-" + currency.format(couponDiscountRefunded));
            hbRefundCouponDiscount.setVisible(true);
            hbRefundCouponDiscount.setManaged(true);
        } else {
            hbRefundCouponDiscount.setVisible(false);
            hbRefundCouponDiscount.setManaged(false);
        }

        if (loyaltyDiscountRefunded > 0.001) {
            lblRefundLoyaltyDiscount.setText("-" + currency.format(loyaltyDiscountRefunded));
            hbRefundLoyaltyDiscount.setVisible(true);
            hbRefundLoyaltyDiscount.setManaged(true);
        } else {
            hbRefundLoyaltyDiscount.setVisible(false);
            hbRefundLoyaltyDiscount.setManaged(false);
        }

        lblRefundTotal.setText(currency.format(refundTotal));
    }

    private void closeDialog() {
        Stage stage = (Stage) btnCancel.getScene().getWindow();
        stage.close();
    }

    private List<SaleItemResponse> buildRefundableItems(SaleResponse sale, List<SaleResponse> previousRefunds) {
        Map<Long, Integer> refundedByProduct = new HashMap<>();
        for (SaleResponse refund : previousRefunds) {
            if (refund.getItems() == null) {
                continue;
            }
            for (SaleItemResponse refundItem : refund.getItems()) {
                if (refundItem.getProdId() == null || refundItem.getQuantity() == null) {
                    continue;
                }
                refundedByProduct.merge(refundItem.getProdId(), Math.abs(refundItem.getQuantity()), Integer::sum);
            }
        }

        List<SaleItemResponse> refundableItems = new ArrayList<>();
        if (sale.getItems() == null) {
            return refundableItems;
        }

        for (SaleItemResponse originalItem : sale.getItems()) {
            if (originalItem.getProdId() == null || originalItem.getQuantity() == null) {
                continue;
            }

            int remainingQuantity = originalItem.getQuantity() - refundedByProduct.getOrDefault(originalItem.getProdId(), 0);
            if (remainingQuantity <= 0) {
                continue;
            }

            SaleItemResponse refundableItem = new SaleItemResponse();
            refundableItem.setSaleItemId(originalItem.getSaleItemId());
            refundableItem.setProdId(originalItem.getProdId());
            refundableItem.setProductName(originalItem.getProductName());
            refundableItem.setQuantity(remainingQuantity);
            refundableItem.setUnitPrice(originalItem.getUnitPrice());
            refundableItems.add(refundableItem);
        }

        return refundableItems;
    }

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

    public static class RefundItem {
        private final int prodId;
        private final String productName;
        private final int quantity;
        private final double unitPrice;

        public RefundItem(int prodId, String productName, int quantity, double unitPrice) {
            this.prodId = prodId;
            this.productName = productName;
            this.quantity = quantity;
            this.unitPrice = unitPrice;
        }

        public int getProdId() {
            return prodId;
        }

        public String getProductName() {
            return productName;
        }

        public int getQuantity() {
            return quantity;
        }

        public double getUnitPrice() {
            return unitPrice;
        }

        public double getTotal() {
            return quantity * unitPrice;
        }
    }

    private record LoadedSaleData(SaleResponse sale, List<SaleItemResponse> refundableItems) {
    }
}
