package com.ecep.contract.controller;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.FxmlUtils;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;

import com.ecep.contract.Desktop;
import com.ecep.contract.SpringApp;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.service.SysConfService;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CurrentEmployee;
import com.ecep.contract.vm.IdentityViewModel;

import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import lombok.Getter;

public class BaseController {
    private static final Logger logger = LoggerFactory.getLogger(BaseController.class);
    public static HashMap<String, Stage> stages = new HashMap<>();

    public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner) {
        return show(clz, owner, null);
    }

    public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner,
            Consumer<T> consumer) {
        String key = clz.getName();
        if (toFront(key)) {
            return null;
        }
        FxmlPath annotation = clz.getAnnotation(FxmlPath.class);
        if (annotation == null) {
            throw new RuntimeException("@FxmlPath is required");
        }

        return FxmlUtils.newLoaderAsyncWithRunLater(annotation.value(), null, loader -> {
            T controller = loader.getController();
            if (consumer != null) {
                consumer.accept(controller);
            }
            controller.show(loader, owner, Modality.NONE, key);
        });
    }

    @SuppressWarnings("unchecked")
    public static <K extends IdentityEntity, M extends IdentityViewModel<K>, T extends BaseController> void show(
            Class<T> clz, M viewModel, Window owner) {
        String key = getKey(clz, viewModel);
        if (toFront(key)) {
            return;
        }
        FxmlPath annotation = clz.getAnnotation(FxmlPath.class);
        if (annotation == null) {
            throw new RuntimeException("@FxmlPath is required");
        }

        FxmlUtils.newLoaderAsyncWithRunLater(annotation.value(), null, loader -> {
            T controller = loader.getController();
            if (controller instanceof AbstEntityController<?, ?>) {
                ((AbstEntityController<?, M>) controller).setViewModel(viewModel);
            }
            controller.show(loader, owner, Modality.NONE, key);
        });
    }

    private static <V> String getKey(Class<?> clz, V viewModel) {
        PropertyDescriptor idProperty = BeanUtils.getPropertyDescriptor(viewModel.getClass(), "id");
        if (idProperty != null) {
            Method readMethod = idProperty.getReadMethod();
            if (readMethod != null) {
                try {
                    Object object = readMethod.invoke(viewModel);
                    if (object instanceof ObservableValue<?> value) {
                        return clz.getName() + "#" + value.getValue();
                    }
                } catch (IllegalAccessException | InvocationTargetException ignored) {
                }
            }
        }
        return clz.getName() + "#" + viewModel.hashCode();
    }

    public static void show(Class<?> clz, String resource, Window owner) {
        String key = clz.getName();
        if (toFront(key)) {
            return;
        }
        FxmlUtils.newLoaderAsyncWithRunLater(resource, null, loader -> {
            BaseController controller = loader.getController();
            controller.show(loader, owner, Modality.NONE, key);
        });
    }

    public static boolean toFront(String key) {
        Stage stage = stages.get(key);
        if (stage != null) {
            stage.toFront();
            if (!stage.isShowing()) {
                stage.show();
            }
            return true;
        }
        return false;
    }

    public static CompletableFuture<Void> shutdown() {
        if (logger.isDebugEnabled()) {
            logger.debug("shutdown");
        }
        // 关闭所有窗口
        CompletableFuture<Void> future = new CompletableFuture<>();
        ArrayList<Stage> list = new ArrayList<>(stages.values());
        if (list.isEmpty()) {
            if (logger.isDebugEnabled()) {
                logger.debug("no stage should to close.");
            }
            future.complete(null);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("try to close stages {}.", list);
            }
            Platform.runLater(() -> {
                try {
                    for (Stage stage : list) {
                        System.out.println("stage = " + stage);
                        stage.close();
                        if (logger.isDebugEnabled()) {
                            logger.debug("stage close {}", stage);
                        }
                    }
                } catch (Exception e) {
                    logger.error("stage close error", e);
                }
                future.complete(null);
            });
        }
        return future;
    }

    public CompletableFuture<Void> runAsync(Runnable runnable) {
        return CompletableFuture.runAsync(runnable, Desktop.instance.getExecutorService())
                .exceptionally(this::handleException);
    }

    /**
     * 窗口标题
     */
    @Getter
    private final StringProperty title = new SimpleStringProperty("窗口");
    /**
     * 窗口是否已经请求关闭
     */
    @Getter
    private boolean closeRequested = false;
    /**
     * 用于管理窗口的Key
     */
    private String stageKey;
    public Label leftStatusLabel;
    public Label rightStatusLabel;
    private Employee currentUser;

    private HashMap<Class<?>, Object> cachedBeans = new HashMap<>();

    public <T> T getBean(Class<T> requiredType) throws BeansException {
        return SpringApp.getBean(requiredType);
    }

    @SuppressWarnings("unchecked")
    public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
        Object object = cachedBeans.get(requiredType);
        if (object == null) {
            object = getBean(requiredType);
            cachedBeans.put(requiredType, object);
        }
        return (T) object;
    }

    public SysConfService getConfService() {
        return getCachedBean(SysConfService.class);
    }

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

    public Employee getCurrentUser() {
        if (currentUser == null) {
            currentUser = getEmployeeService().findById(Desktop.instance.getActiveEmployeeId());
        }
        return currentUser;
    }

    public CurrentEmployee getCurrentEmployee() {
        return Desktop.instance.getActiveEmployee();
    }

    public Locale getLocale() {
        CurrentEmployee currentEmployee = getCurrentEmployee();
        if (currentEmployee == null) {
            return Locale.getDefault();
        }
        return currentEmployee.localeProperty().get();
    }

    public String getMessage(String code, Object... args) {
        return SpringApp.getMessage(code, args, getLocale());
    }

    protected void setStatus() {
        setStatus(Strings.EMPTY);
    }

    public void setStatus(String status) {
        if (leftStatusLabel == null) {
            return;
        }
        Platform.runLater(() -> leftStatusLabel.textProperty().set(status));
    }

    public void setRightStatus(String status) {
        if (rightStatusLabel == null) {
            return;
        }
        Platform.runLater(() -> rightStatusLabel.textProperty().set(status));
    }

    /**
     * FXML loader 载入时会自动运行此函数
     */
    public void initialize() {

    }

    public void show(Stage stage) {
        stage.titleProperty().bind(title);
        stage.setOnShown(this::onShown);
        stage.setOnShowing(this::onShowing);
        stage.setOnCloseRequest(this::onCloseRequest);
        stage.setOnHiding(this::onHiding);
        stage.setOnHidden(this::onHidden);
        stage.show();
    }

    public Stage show(FXMLLoader loader, Window owner, Modality modality) {
        Scene scene = new Scene(loader.getRoot());
        Stage stage = new Stage();
        if (owner != null) {
            stage.initOwner(owner);
            stage.initModality(modality);
        }
        stage.setScene(scene);
        show(stage);

        return stage;
    }

    protected Stage show(FXMLLoader loader, Window window, Modality modality, String stageKey) {
        Stage stage = show(loader, window, modality);
        stages.put(stageKey, stage);
        this.stageKey = stageKey;
        return stage;
    }

    public void onShowing(WindowEvent windowEvent) {

    }

    public void onShown(WindowEvent windowEvent) {
        setStatus("");
        setRightStatus("");
    }

    /**
     * 当窗口要关闭前
     *
     * @param windowEvent event
     */
    public void onCloseRequest(WindowEvent windowEvent) {
        closeRequested = true;
    }

    public void onHiding(WindowEvent windowEvent) {

    }

    public void onHidden(WindowEvent windowEvent) {
        if (stageKey != null) {
            stages.remove(stageKey);
        }

    }

    /**
     * 处理错误
     *
     * @param ex 错误信息
     * @return Void
     */
    public Void handleException(Throwable ex) {
        handleException(ex.getMessage(), ex);
        return null;
    }

    public void handleException(String message, Throwable ex) {
        if (logger.isErrorEnabled()) {
            logger.error(message, ex);
        }
        UITools.showExceptionAndWait(message, ex);
    }
}
