package ru.kirillius.pf.sdn.web.RPC;

import org.json.JSONArray;
import org.json.JSONObject;
import ru.kirillius.json.JSONUtility;
import ru.kirillius.json.rpc.Annotations.JRPCArgument;
import ru.kirillius.json.rpc.Annotations.JRPCMethod;
import ru.kirillius.pf.sdn.External.API.GitSubscription;
import ru.kirillius.pf.sdn.External.API.LocalFilesystemSubscription;
import ru.kirillius.pf.sdn.InMemoryLogHandler;
import ru.kirillius.pf.sdn.core.*;
import ru.kirillius.pf.sdn.web.ProtectedMethod;

import java.io.IOException;
import java.util.stream.Collectors;

/**
 * JSON-RPC handler for system control operations and component configuration management.
 */
public class System implements RPC {
    private final Context context;

    /**
     * Creates the handler bound to the application context.
     */
    public System(Context context) {
        this.context = context;
    }

    /**
     * Requests an application restart.
     */
    @ProtectedMethod
    @JRPCMethod
    public void restart() {
        context.requestExit(true);
    }

    /**
     * Requests an application shutdown.
     */
    @ProtectedMethod
    @JRPCMethod
    public void shutdown() {
        context.requestExit(false);
    }

    /**
     * Returns the current configuration as JSON.
     */
    @ProtectedMethod
    @JRPCMethod
    public JSONObject getConfig() {
        return JSONUtility.serializeStructure(context.getConfig());
    }

    @ProtectedMethod
    @JRPCMethod
    public void setConfig(@JRPCArgument(name = "config") JSONObject json) throws Exception {
        var config = JSONUtility.deserializeStructure(json, Config.class);
        var initial = new Config();
        initial.merge(context.getConfig());
        context.getConfig().merge(config);
        context.getEventsHandler().getConfigChangeEvent().invoke(ContextEventsHandler.ConfigChangeContext.builder()
                .initial(initial)
                .current(config)
                .build());
    }


    /**
     * Indicates whether the configuration has unsaved changes.
     */
    @ProtectedMethod
    @JRPCMethod
    public boolean isConfigChanged() {
        return context.getConfig().isModified();
    }

    @ProtectedMethod
    @JRPCMethod
    public void saveConfig() {
        try {
            Config.store(context.getConfig(), context.getLauncherConfig().getConfigFile());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Retrieves the latest version available for update.
     */
    @ProtectedMethod
    @JRPCMethod
    public String getVersionForUpdate() {
        return context.getServiceManager().getService(AppUpdateService.class).checkVersionForUpdate();
    }

    /**
     * Returns the currently installed application version.
     */
    @ProtectedMethod
    @JRPCMethod
    public String getAppVersion() {
        return context.getServiceManager().getService(AppUpdateService.class).checkVersionForUpdate();
    }

    /**
     * Provides additional metadata about available versions.
     */
    @ProtectedMethod
    @JRPCMethod
    public JSONObject getVersionInfo() {
        var updateService = context.getServiceManager().getService(AppUpdateService.class);
        var json = new JSONObject();
        json.put("available", updateService.getLatestVersion());
        json.put("current", updateService.getAppVersion());
        json.put("downloaded", updateService.getDownloadedVersion());
        return json;
    }

    @ProtectedMethod
    @JRPCMethod
    public void doUpdate() {
        var updateService = context.getServiceManager().getService(AppUpdateService.class);
        updateService.updateApp();
    }

    @ProtectedMethod
    @JRPCMethod
    public JSONArray getRepositoryTypes() {
        var array = new JSONArray();
        array.put(GitSubscription.class.getName());
        array.put(LocalFilesystemSubscription.class.getName());

        return array;
    }

    /**
     * Returns the list of enabled component class names.
     */
    @ProtectedMethod
    @JRPCMethod
    public JSONArray getEnabledComponents() {
        return JSONUtility.serializeCollection(context.getConfig().getEnabledComponents().stream().map(Class::getName).toList(), String.class, null);
    }


    /**
     * Updates the set of enabled components according to the provided class names.
     */
    @SuppressWarnings("unchecked")
    @ProtectedMethod
    @JRPCMethod
    public void setEnabledComponents(@JRPCArgument(name = "components") JSONArray components) {
        context.getConfig().setEnabledComponents(JSONUtility.deserializeCollection(components, String.class, null).map(s -> {
            try {
                return (Class<? extends Component<?>>) Class.forName(s);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("Unable to load Component class " + s, e);
            }
        }).collect(Collectors.toList()));
        context.getServiceManager().getService(ComponentHandlerService.class).syncComponentsWithConfig();
    }

    /**
     * Returns the list of component classes available to the launcher.
     */
    @ProtectedMethod
    @JRPCMethod
    public JSONArray getAvailableComponents() {
        return JSONUtility.serializeCollection(context.getLauncherConfig().getAvailableComponentClasses().stream().map(Class::getName).toList(), String.class, null);
    }


    /**
     * Returns the configuration object for the specified component.
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    @ProtectedMethod
    @JRPCMethod
    public JSONObject getComponentConfig(@JRPCArgument(name = "component") String componentName) throws ClassNotFoundException {
        var config = context.getConfig().getComponentsConfig().getConfig((Class) Class.forName(componentName));
        return JSONUtility.serializeStructure(config);
    }


    /**
     * Updates the configuration of the specified component and reloads it.
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    @ProtectedMethod
    @JRPCMethod
    public void setComponentConfig(@JRPCArgument(name = "component") String componentName, @JRPCArgument(name = "config") JSONObject config) throws ClassNotFoundException {
        var cls = (Class) Class.forName(componentName);
        var configClass = Component.getConfigClass(cls);
        context.getConfig().getComponentsConfig().setConfig(cls, JSONUtility.deserializeStructure(config, configClass));
        context.getServiceManager().getService(ComponentHandlerService.class).reloadComponents(cls);
        context.getConfig().getComponentsConfig().getConfig(cls);
    }

    @ProtectedMethod
    @JRPCMethod
    public JSONArray getLogs() {
        return JSONUtility.serializeCollection(InMemoryLogHandler.getMessages(), String.class, null);
    }

    @ProtectedMethod
    @JRPCMethod
    public void clearLogs() {
        InMemoryLogHandler.clear();
    }
}
