package org.example.petshopdesktop.controllers;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.DesktopImageSupport;
import org.example.petshopdesktop.util.TableViewSupport;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * The controller for any operations in the products view
 */
public class ProductController {

    @FXML
    private Button btnAdd;

    @FXML
    private Button btnDelete;

    @FXML
    private Button btnEdit;

    @FXML
    private Button btnRefresh;

    @FXML
    private Label lblStatus;

    @FXML
    private TableColumn<ProductDTO, String> colProductCategory;

    @FXML
    private TableColumn<ProductDTO, String> colProductDesc;

    @FXML
    private TableColumn<ProductDTO, Integer> colProductId;

    @FXML
    private TableColumn<ProductDTO, String> colProductImage;

    @FXML
    private TableColumn<ProductDTO, String> colProductName;

    @FXML
    private TableColumn<ProductDTO, Double> colProductPrice;

    @FXML
    private TableView<ProductDTO> tvProducts;

    @FXML
    private ComboBox<DropdownOption> cbCategoryFilter;

    @FXML
    private TextField txtSearch;

    //data declaration
    private ObservableList<ProductDTO> data = FXCollections.observableArrayList(); //empty
    private String mode = null;

    /**
     * Set up the table view for products and display it when starting up
     */
    @FXML
    void initialize() {
        //Disable buttons until a row is selected
        btnEdit.setDisable(true);
        btnDelete.setDisable(true);
        //Enable multiple selection
        tvProducts.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
        //set up table columns
        colProductId.setCellValueFactory(new PropertyValueFactory<ProductDTO,Integer>("prodId"));
        colProductImage.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("imageUrl"));
        colProductName.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("prodName"));
        colProductPrice.setCellValueFactory(new PropertyValueFactory<ProductDTO,Double>("prodPrice"));
        colProductCategory.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("categoryName"));
        colProductDesc.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("prodDesc"));
        TableViewSupport.applyCurrencyColumn(colProductPrice);
        configureImageColumn(colProductImage);
        loadCategoryFilter();

        displayProduct();
        TableViewSupport.installDoubleClickAction(tvProducts, selected -> openDialog(selected, "Edit"));

        //EventListener to Enable buttons when a row is selected
        tvProducts.getSelectionModel().selectedItemProperty().addListener(
                (observable, oldValue, newValue) -> {
                    boolean hasSelection = newValue != null;
                    btnEdit.setDisable(!hasSelection);
                    btnDelete.setDisable(!hasSelection);
                }
        );

        //EventListener to search when text is changed on searchbar
        txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
            applyFilters();
        });

        cbCategoryFilter.valueProperty().addListener((observable, oldValue, newValue) -> applyFilters());

        //EventListener for DELETE key press
        tvProducts.setOnKeyPressed(event -> {
            if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
                if (tvProducts.getSelectionModel().getSelectedItem() != null) {
                    btnDeleteClicked(null);
                }
            }
        });

    }

    /**
     * Display the productDTO to table view
     */
    private void displayProduct(){
        new Thread(() -> {
            try {
                List<ProductResponse> products = ProductApi.getInstance().listProducts(null, selectedCategoryId());
                List<ProductDTO> productDTOs = products.stream()
                    .map(this::mapToProductDTO)
                    .collect(Collectors.toList());

                Platform.runLater(() -> {
                    data.setAll(productDTOs);
                    tvProducts.setItems(data);
                });
            } catch (Exception e) {
                Platform.runLater(() -> {
                    System.out.println("Error while fetching table data: " + e.getMessage());
                    ActivityLogger.getInstance().logException(
                        "ProductController.displayProduct",
                        e,
                        "Fetching product data for table display");
                });
            }
        }).start();
    }

    /**
     * open a new dialog for adding a product
     * @param event click event for button
     */
    @FXML
    void btnRefresh(ActionEvent event) {
        txtSearch.clear();
        if (cbCategoryFilter != null) {
            cbCategoryFilter.getSelectionModel().selectFirst();
        }
        tvProducts.getSortOrder().clear();
        displayProduct();
        TableViewSupport.flashStatus(lblStatus, "Refreshed");
    }

    @FXML
    void btnAddClicked(ActionEvent event) {
        mode = "Add";
        openDialog(null,mode);
    }

    /**
     * Delete selected product(s) when delete is clicked
     * @param event click event for button
     */
    @FXML
    void btnDeleteClicked(ActionEvent event) {
        //get selected products
        var selectedProducts = tvProducts.getSelectionModel().getSelectedItems();
        if (selectedProducts.isEmpty()) return;

        //ask user to confirm
        Alert question = new Alert(Alert.AlertType.CONFIRMATION);
        question.setHeaderText("Please confirm delete");
        String message = selectedProducts.size() == 1
            ? "Are you sure you want to delete this product?"
            : "Are you sure you want to delete " + selectedProducts.size() + " products?";
        question.setContentText(message);
        question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
        Optional<ButtonType> result = question.showAndWait();

        //if confirmed, start deletion
        if (result.isPresent() && result.get() == ButtonType.OK) {
            List<Long> ids = selectedProducts.stream()
                .map(p -> (long) p.getProdId())
                .collect(Collectors.toList());

            try {
                ProductApi.getInstance().deleteProducts(ids);
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setHeaderText("Database Operation Confirmed");
                alert.setContentText("Successfully deleted " + ids.size() + " product(s)");
                alert.showAndWait();
            } catch (Exception e) {
                ActivityLogger.getInstance().logException(
                    "ProductController.btnDeleteClicked",
                    e,
                    "Deleting products");
                Alert alert = new Alert(Alert.AlertType.ERROR);
                alert.setHeaderText("Delete Operation Failed");
                alert.setContentText(e.getMessage());
                alert.showAndWait();
            }

            //refresh display and reset inputs
            displayProduct();
            btnDelete.setDisable(true);
            btnEdit.setDisable(true);
            txtSearch.setText("");
        }
    }

    /**
     * Open a new dialog for editing a product
     * @param event click event for button
     */
    @FXML
    void btnEditClicked(ActionEvent event) {
        //set selected product
        ProductDTO selectedProduct = tvProducts.getSelectionModel().getSelectedItem();

        if (selectedProduct != null) {
            mode = "Edit";
            openDialog(selectedProduct, mode);
        }
    }

    /**
     * Filter the table given the string from the searchbar
     * @param filter word to filter table
     */
    private void displayFilteredProduct(String filter){
        if ((txtSearch.getText() == null || txtSearch.getText().isEmpty()) && selectedCategoryId() == null){
            displayProduct();
        } else {
            new Thread(() -> {
                try {
                    List<ProductResponse> products = ProductApi.getInstance().listProducts(filter, selectedCategoryId());
                    List<ProductDTO> productDTOs = products.stream()
                        .map(this::mapToProductDTO)
                        .collect(Collectors.toList());

                    Platform.runLater(() -> {
                        data.setAll(productDTOs);
                        tvProducts.setItems(data);
                    });
                } catch (Exception e) {
                    Platform.runLater(() -> {
                        System.out.println("Error while fetching table data: " + e.getMessage());
                        ActivityLogger.getInstance().logException(
                            "ProductController.displayFilteredProduct",
                            e,
                            String.format("Filtering products with keyword: %s", filter));
                    });
                }
            }).start();
        }
    }

    private void applyFilters() {
        displayFilteredProduct(txtSearch.getText());
    }

    private void loadCategoryFilter() {
        new Thread(() -> {
            try {
                List<DropdownOption> options = new ArrayList<>();
                DropdownOption all = new DropdownOption();
                all.setId(null);
                all.setLabel("All Categories");
                options.add(all);
                options.addAll(DropdownApi.getInstance().getProductCategories());
                Platform.runLater(() -> {
                    cbCategoryFilter.setItems(FXCollections.observableArrayList(options));
                    cbCategoryFilter.getSelectionModel().selectFirst();
                });
            } catch (Exception e) {
                Platform.runLater(() -> ActivityLogger.getInstance().logException(
                    "ProductController.loadCategoryFilter",
                    e,
                    "Loading category filter options"));
            }
        }).start();
    }

    private Long selectedCategoryId() {
        DropdownOption option = cbCategoryFilter.getValue();
        return option == null ? null : option.getId();
    }

    /**
     * Function to open the new Dialog for edit or adding
     * depending on the mode given
     * @param product the product entity for editing, null if adding
     * @param mode the mode the dialog should be in
     */
    private void openDialog(ProductDTO product, String mode){
        //Get new view
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml"));
        Scene scene = null;
        try{
            scene = new Scene(fxmlLoader.load());
        } catch (IOException e) {
            ActivityLogger.getInstance().logException(
                "ProductController.openDialog",
                e,
                String.format("Loading product dialog view in %s mode", mode));
            throw new RuntimeException(e);
        }
        ProductDialogController dialogController = fxmlLoader.getController(); //controller associated with this view
        dialogController.setMode(mode);

        //Open the dialog depending on the mode
        if(mode.equals("Edit")){
            //Make it display suppliers details in dialog
            dialogController.displayProductDetails(product);
        }
        Stage dialogStage = new Stage();
        dialogStage.initModality(Modality.APPLICATION_MODAL); //make it modal
        if(mode.equals("Add")){
            dialogStage.setTitle("Add Product");
        }
        else {
            dialogStage.setTitle("Edit Product");
        }
        dialogStage.setScene(scene);
        dialogStage.showAndWait();

        //When dialog closes update table view and disable edit and delete buttons, and reset search bar
        displayProduct();
        btnDelete.setDisable(true);
        btnEdit.setDisable(true);
        txtSearch.setText("");
    }

    private ProductDTO mapToProductDTO(ProductResponse response) {
        return new ProductDTO(
            response.getProdId().intValue(),
            response.getProdName(),
            response.getProdPrice().doubleValue(),
            0,
            response.getCategoryName(),
            response.getProdDesc(),
            response.getImageUrl()
        );
    }

    private void configureImageColumn(TableColumn<ProductDTO, String> column) {
        column.setCellFactory(col -> new TableCell<>() {
            private final ImageView imageView = new ImageView();
            private final StackPane container = new StackPane(imageView);
            {
                container.setAlignment(Pos.CENTER);
            }

            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if (empty || item == null || item.isBlank()) {
                    setGraphic(null);
                    return;
                }
                DesktopImageSupport.loadImageInto(imageView, item, 48, 48);
                setGraphic(container);
            }
        });
    }

}
