package com.ecep.contract.controller.table;

import java.io.File;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.RefreshableSkin;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.util.TableViewUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.IdentityViewModel;

import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
import lombok.Getter;
import lombok.Setter;

/**
 * 表格显示组件
 *
 * @param <K>  父数据类， 用于筛选表格内数据的关联类
 * @param <KV> 父数据视图类
 * @param <T>  表格数据类
 * @param <TV> 表格数据视图类
 */
public abstract class AbstEntityTableTabSkin<C extends AbstEntityController<K, KV>,
        // 父数据类
        K extends IdentityEntity, KV extends IdentityViewModel<K>,
        // 表格数据类
        T extends IdentityEntity, TV extends IdentityViewModel<T>>
        extends AbstEntityBasedTabSkin<C, K, KV>
        implements TableOfTabSkin<K, T, TV>, EditableEntityTableTabSkin<T, TV>, RefreshableSkin {
    private static final Logger logger = LoggerFactory.getLogger(AbstEntityTableTabSkin.class);
    protected CompletableFuture<Void> loadedFuture;
    protected final SimpleBooleanProperty changed = new SimpleBooleanProperty(false);
    protected ObservableList<TV> dataSet = FXCollections.observableArrayList();
    public TextField searchKeyField;
    public Button searchBtn;
    public TableView<TV> table;
    @Getter
    @Setter
    private boolean dragAndDrop = false;

    public AbstEntityTableTabSkin(C controller) {
        super(controller);
    }

    @SuppressWarnings("unchecked")
    public <CLZ> CLZ getCachedBean(Class<CLZ> requiredType) throws BeansException {
        return controller.getCachedBean(requiredType);
    }

    public EmployeeService getEmployeeService() {
        return getCachedBean(EmployeeService.class);
    }

    @Override
    public TableView<TV> getTableView() {
        return table;
    }

    protected ViewModelService<K, KV> getParentService() {
        ViewModelService<K, KV> service = controller.getViewModelService();
        if (service == null) {
            if (logger.isWarnEnabled()) {
                logger.warn("ViewModelService is null");
            }
        }
        return service;
    }

    protected abstract ViewModelService<T, TV> getViewModelService();

    @Override
    public List<TV> loadTableData(K parent) {
        if (parent == null) {
            // parent must not be null
            return null;
        }
        Map<String, Object> spec = getSpecification(parent);
        ViewModelService<T, TV> service = getViewModelService();
        if (service == null) {
            if (logger.isWarnEnabled()) {
                logger.warn("{} ViewModelService is null", this);
            }
        }
        Page<T> page = service.findAll(spec, getPageable());
        updateFooter(page);
        return page.map(service::from).toList();
    }

    @Override
    public Map<String, Object> getSpecification() {
        if (searchKeyField == null) {
            return null;
        }
        return getViewModelService().getSpecification(searchKeyField.getText());
    }

    public Pageable getPageable() {
        Sort sort = getSortByTable();
        // TODO 待完善 currentPageable
        // return currentPageable.withSort(sort);
        return Pageable.unpaged(sort);
    }

    protected void updateFooter(Page<T> page) {

    }

    private ObjectProperty<Consumer<List<File>>> dragAndDropFileHandler;

    public void setDragAndDropFileHandler(Consumer<List<File>> handler) {
        if (dragAndDropFileHandler == null) {
            dragAndDropFileHandler = new SimpleObjectProperty<>(handler);
        } else {
            dragAndDropFileHandler.set(handler);
        }
    }

    public Consumer<List<File>> getDragAndDropFileHandler() {
        return dragAndDropFileHandler == null ? null : dragAndDropFileHandler.get();
    }

    protected final CompletableFuture<K> getParentFuture() {
        return getFuture();
    }

    protected K getParent() {
        return getEntity();
    }

    @Override
    public BooleanProperty changeProperty() {
        return changed;
    }

    protected void changed(TV row) {
        row.getChanged().set(true);
        setChanged(true);
    }

    protected <X> void changed(
            TableColumn.CellEditEvent<TV, X> event,
            Function<TV, Property<X>> supplier) {
        TV row = event.getRowValue();
        supplier.apply(row).setValue(event.getNewValue());
        changed(row);
    }

    public void initializeUIComponents() {
        listenTabSelectionChanged();
    }

    protected void bindNumberColumn(TableColumn<TV, Number> column, Function<TV, Property<Number>> propertyFun) {
        column.setCellValueFactory(cellData -> propertyFun.apply(cellData.getValue()));
    }

    protected void bindColumn(TableColumn<TV, String> column, Function<TV, Property<String>> propertyFun) {
        column.setCellValueFactory(param -> propertyFun.apply(param.getValue()));
        column.setCellFactory(TextFieldTableCell.forTableColumn());
        column.setOnEditCommit(event -> changed(event, propertyFun));
    }

    protected void bindLocalDateColumn(TableColumn<TV, LocalDate> column,
            Function<TV, Property<LocalDate>> propertyFun) {
        column.setCellValueFactory(param -> propertyFun.apply(param.getValue()));
        column.setCellFactory(LocalDateFieldTableCell.forTableColumn());
    }

    protected void bindLocalDateTimeColumn(TableColumn<TV, LocalDateTime> column,
            Function<TV, Property<LocalDateTime>> propertyFun) {
        column.setCellValueFactory(param -> propertyFun.apply(param.getValue()));
        column.setCellFactory(param -> new LocalDateTimeTableCell<>());
    }

    @Override
    public void onTabShown() {
        // 当第一次显示 tab 时，初始化表格
        if (loadedFuture == null) {
            loadedFuture = runAsync(() -> {
                initializeTab();
                initializeTable();
                loadTableDataSet();
            });
        }
    }

    /**
     * 加载表格数据
     */
    public void loadTableDataSet() {
        CompletableFuture<K> parentFuture = getParentFuture();
        if (parentFuture == null) {
            loadTableDataSet(null);
        } else {
            parentFuture.thenAcceptAsync(this::loadTableDataSet).exceptionally(this::handleException);
        }
    }

    @Override
    public void loadTableDataSet(K parent) {
        Platform.runLater(() -> {
            dataSet.clear();
            CompletableFuture.runAsync(() -> {
                List<TV> models = loadTableData(parent);
                Platform.runLater(() -> {
                    try {
                        dataSet.setAll(models);
                    } catch (Exception e) {
                        handleException(e);
                    }
                });
            }).exceptionally(this::handleException);
        });
    }

    @Override
    public void initializeTab() {
        if (searchKeyField != null) {
            searchKeyField.setOnKeyReleased(event -> {
                if (event.getCode() == KeyCode.ENTER) {
                    searchBtn.fire();
                }
            });
        }
        if (searchBtn != null) {
            searchBtn.setOnAction(this::onTableRefreshAction);
        }
    }

    public void initializeTable() {
        TableView<TV> tableView = getTableView();
        tableView.setItems(dataSet);
        tableView.setOnDragOver(this::onTableDragOverAction);
        tableView.setOnDragDropped(this::onTableDragDroppedAction);
        TableViewUtils.bindDoubleClicked(tableView, this::onTableRowDoubleClickedAction);

        if (tableView.contextMenuProperty().get() == null) {
            ContextMenu contextMenu = new ContextMenu();
            createContextMenu(contextMenu);
            tableView.setContextMenu(contextMenu);
        }

        // 注册Ctrl+V快捷键
        KeyCodeCombination keyCodeCombination = new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN);
        KeyCodeCombination ctrlSCombination = new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN);
        KeyCodeCombination ctrlRCombination = new KeyCodeCombination(KeyCode.R, KeyCombination.SHORTCUT_DOWN);

        tableView.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
            if (keyCodeCombination.match(event)) {
                handleCtrlVPaste(tableView, event);
                event.consume();
            }
            if (ctrlSCombination.match(event)) {
                controller.saveTabSkins();
                event.consume();
            }
            if (ctrlRCombination.match(event)) {

                controller.refresh();
                event.consume();
            }
        });
    }

    protected void handleCtrlVPaste(TableView<TV> tableView, KeyEvent event) {
        Clipboard clipboard = Clipboard.getSystemClipboard();
        if (clipboard.hasFiles()) {
            Consumer<List<File>> handler = getDragAndDropFileHandler();
            if (handler == null) {
                UITools.showExceptionAndWait("请设置拖拽文件处理函数", null);
                return;
            }
            List<File> files = clipboard.getFiles();
            CompletableFuture.runAsync(() -> {
                try {
                    handler.accept(files);
                } catch (Exception e) {
                    UITools.showExceptionAndWait("粘贴文件出错", e);
                }
            });
        }
    }

    protected void createContextMenu(ContextMenu contextMenu) {
        MenuItem item2 = new MenuItem("刷新");
        item2.setOnAction(this::onTableRefreshAction);
        contextMenu.getItems().add(item2);

        if (this instanceof EditableEntityTableTabSkin) {
            MenuItem item1 = new MenuItem("新建");
            item1.setOnAction(this::onTableAddAction);

            MenuItem item3 = new MenuItem("删除");
            item3.setOnAction(this::onTableDeleteAction);

            contextMenu.getItems().addAll(item1, item3);
        }
    }

    /**
     * 当有东西拖动到表格上时触发
     * 默认为实现任何处理
     * #acceptFileTransferMode 支持文件移动操作
     *
     * @param event 拖动事件
     */
    protected void onTableDragOverAction(DragEvent event) {
        if (isDragAndDrop()) {
            acceptFileTransferMode(event);
        }
    }

    protected void acceptFileTransferMode(DragEvent event) {
        Dragboard dragboard = event.getDragboard();
        if (dragboard.hasFiles()) {
            event.acceptTransferModes(TransferMode.MOVE);
        }
        event.consume();
    }

    /**
     * 当有东西拖动到表格时触发
     *
     * @param event 拖动事件
     */
    protected void onTableDragDroppedAction(DragEvent event) {
        if (isDragAndDrop()) {
            Dragboard dragboard = event.getDragboard();
            boolean success = false;
            if (dragboard.hasFiles()) {
                Consumer<List<File>> handler = getDragAndDropFileHandler();
                if (handler == null) {
                    UITools.showExceptionAndWait("请设置拖拽文件处理函数", null);
                    return;
                }
                List<File> files = dragboard.getFiles();
                CompletableFuture.runAsync(() -> {
                    try {
                        handler.accept(files);
                    } catch (Exception e) {
                        UITools.showExceptionAndWait("移动文件出错", e);
                    }
                });
            }
            event.setDropCompleted(success);
            event.consume();
        }
    }

    /**
     * 当表格行被双击时触发
     *
     * @param item 被双击的行数据
     */
    protected void onTableRowDoubleClickedAction(TV item) {
    }

    public CompletableFuture<?> refresh() {
        CompletableFuture<?> future = new CompletableFuture<>();
        CompletableFuture.runAsync(() -> {
            try {
                loadTableDataSet();
                future.complete(null);
            } catch (Exception e) {
                future.completeExceptionally(e);
            }
        });
        return future;
    }

    protected void onTableRefreshAction(ActionEvent event) {
        loadTableDataSet();
    }

    // @SuppressWarnings("unchecked")
    protected TV createNewViewModel() {
        ViewModelService<T, TV> service = getViewModelService();
        return service.createNewViewModel();

        // Class<?> clz = getClass();
        // Method method = BeanUtils.findMethodWithMinimalParameters(clz,
        // "loadRowData");
        // if (method == null) {
        // throw new RuntimeException(clz.getName() + " unable find loadRowData
        // method!");
        // }
        // Class<?>[] types = method.getParameterTypes();
        // if (types.length == 0) {
        // throw new RuntimeException(clz.getName() + " loadRowData method need a
        // parameter!");
        // }
        // if (types[0] == BaseViewModel.class) {
        // throw new RuntimeException(clz.getName() + " 需要继承实现 loadRowData 方法");
        // }
        // return (TV) BeanUtils.instantiateClass(types[0]);
    }

    protected void onTableAddAction(ActionEvent event) {
        try {
            TV viewModel = createNewViewModel();
            dataSet.add(viewModel);
        } catch (Exception e) {
            handleException(e);
        }
    }

    protected void onTableDeleteAction(ActionEvent event) {
        ObservableList<TV> selectedItems = getTableView().getSelectionModel().getSelectedItems();
        if (selectedItems.isEmpty()) {
            return;
        }
        if (!UITools.showConfirmDialog("删除行", "确认删除选中的" + selectedItems.size() + "条数据？")) {
            return;
        }
        for (TV selectedItem : new ArrayList<>(selectedItems)) {
            if (deleteRow(selectedItem)) {
                dataSet.remove(selectedItem);
            }
        }
    }

    protected boolean deleteRow(TV row) {
        T entity = loadRowData(row);
        if (entity != null) {
            try {
                deleteRowData(entity);
                return true;
            } catch (UnsupportedOperationException e) {
                UITools.showExceptionAndWait("删除出错，此操作不支持", e);
            } catch (Exception e) {
                UITools.showExceptionAndWait("删除出错", e);
            }
        }
        return false;
    }

    public TV getSelectedItem() {
        return getTableView().getSelectionModel().getSelectedItem();
    }

    /**
     * 需要实现 #loadRowData 和 #saveRow 方法
     *
     * @param event
     * @param function
     * @param <P>
     */
    protected <P> void acceptCellEditEvent(TableColumn.CellEditEvent<TV, P> event, Function<TV, Property<P>> function) {
        TV row = event.getRowValue();
        Property<P> property = function.apply(row);
        property.setValue(event.getNewValue());
        try {
            saveRow(row);
        } catch (Exception e) {
            UITools.showExceptionAndWait("保存出错", e);
        }
    }

    protected T createNewEntity(TV row) {
        return getViewModelService().createNewEntity();

        // Class<?> viewModelClz = row.getClass();
        // Method method = BeanUtils.findMethodWithMinimalParameters(viewModelClz,
        // "copyTo");
        // if (method == null) {
        // throw new RuntimeException(viewModelClz.getName() + " unable find copyTo
        // method!");
        // }
        // Class<?>[] types = method.getParameterTypes();
        // if (types.length == 0) {
        // throw new RuntimeException(viewModelClz.getName() + " copyTo method need a
        // parameter!");
        // }
        // return (T) BeanUtils.instantiateClass(types[0]);
    }

    protected void saveRow(TV row) {
        T entity = loadRowData(row);
        if (entity == null) {
            entity = createNewEntity(row);
        }
        if (row.copyTo(entity)) {
            T saved = saveRowData(entity);
            row.update(saved);
        }
        if (row.isChanged()) {
            row.setChanged(false);
        }
    }

    public List<Sort.Order> getTableOrders() {
        return TableViewUtils.getOrders(getTableView());
    }

    public Sort getSortByTable() {
        return Sort.by(getTableOrders());
    }

    @Override
    public void deleteRowData(T entity) {
        getViewModelService().delete(entity);
    }

    @Override
    public T loadRowData(TV row) {
        return getViewModelService().findById(row.getId().get());
    }

    @Override
    public T saveRowData(T entity) {
        return getViewModelService().save(entity);
    }
}
