package com.ecep.contract.util;

import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;

import com.ecep.contract.Desktop;
import com.ecep.contract.SpringApp;
import com.ecep.contract.model.SysConf;
import com.ecep.contract.service.SysConfService;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Control;
import lombok.Getter;
import lombok.Setter;

public abstract class AbstractConfigBounder<T> implements ConfigBounder {
    @Setter
    private SysConfService confService;
    String key;
    SysConf conf;
    @Getter
    @Setter
    Control control;
    @Getter
    @Setter
    javafx.util.StringConverter<T> converter;
    @Setter
    Property<T> property;
    ObjectProperty<LocalDateTime> modified = new SimpleObjectProperty<>(this, "modified");
    ObjectProperty<LocalDateTime> created = new SimpleObjectProperty<>(this, "created");

    public AbstractConfigBounder(String key) {
        this.key = key;
    }

    @Override
    public CompletableFuture<Void> runAsync(Runnable runnable) {
        return CompletableFuture.runAsync(runnable, Desktop.instance.getExecutorService()).exceptionally(ex -> {
            ex.printStackTrace();
            return null;
        });
    }

    @Override
    public SysConfService getConfService() {
        if (confService == null) {
            confService = SpringApp.getBean(SysConfService.class);
        }
        return confService;
    }

    @Override
    public void initialize() {
        getControl().setDisable(true);
        runAsync(this::asyncHandle);
    }

    protected void asyncHandle() {
        conf = getConfService().findById(key);
        if (conf == null) {
            conf = new SysConf();
            conf.setId(key);
            conf.setCreated(LocalDateTime.now());
            conf.setModified(LocalDateTime.now());
            conf = getConfService().save(conf);
        }
        Property<T> property = getProperty();
        property.addListener(this::propertyChanged);
        bindBidirectional(property);
        modified.set(conf.getModified());
        created.set(conf.getCreated());
        getControl().setDisable(false);
    }

    public Property<T> getProperty() {
        if (property == null) {
            T value = getConverter().fromString(conf.getValue());
            property = createProperty(value);
            setProperty(property);
        }
        return property;
    }

    public ObjectProperty<LocalDateTime> getModifiedProperty() {
        return modified;
    }

    public ObjectProperty<LocalDateTime> getCreatedProperty() {
        return created;
    }

    @SuppressWarnings("unchecked")
    protected Property<T> createProperty(T value) {
        if (value instanceof Boolean b) {
            return (Property<T>) new SimpleBooleanProperty(b);
        } else if (value instanceof Integer i) {
            return (Property<T>) new SimpleIntegerProperty(i);
        } else if (value instanceof Double d) {
            return (Property<T>) new SimpleDoubleProperty(d);
        } else if (value instanceof String s) {
            return (Property<T>) new SimpleStringProperty(s);
        }
        return new SimpleObjectProperty<>(value);
    }

    abstract void bindBidirectional(Property<T> property);

    void propertyChanged(ObservableValue<? extends T> observable, T oldValue,
            T newValue) {
        conf.setValue(getConverter().toString(newValue));
        conf.setModified(LocalDateTime.now());
        save();
    }

    public void save() {
        SysConf saved = getConfService().save(conf);
        modified.set(saved.getModified());
        created.set(saved.getCreated());
    }

}
