package ru.kirillius.pf.sdn.web;


import lombok.Getter;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import ru.kirillius.json.rpc.Servlet.JSONRPCServlet;
import ru.kirillius.pf.sdn.core.AppService;
import ru.kirillius.pf.sdn.core.Auth.AuthManager;
import ru.kirillius.pf.sdn.core.Context;
import ru.kirillius.pf.sdn.web.RPC.Auth;
import ru.kirillius.pf.sdn.web.RPC.NetworkManager;
import ru.kirillius.pf.sdn.web.RPC.RPC;
import ru.kirillius.pf.sdn.web.RPC.SubscriptionManager;
import ru.kirillius.pf.sdn.web.RPC.System;
import ru.kirillius.utils.logging.SystemLogger;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;
import java.util.Set;

/**
 * Hosts the web UI and JSON-RPC API backed by an embedded Jetty server.
 */
public class WebService extends AppService {


    /**
     * Stops the embedded HTTP server.
     */
    @Override
    public void close() throws IOException {
        try {
            httpServer.stop();
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    /**
     * Embedded Jetty server configured with static resources and RPC servlet.
     */
    private class HTTPServer extends Server {
        public final static String ANY_HOST = "0.0.0.0";
        private final static String LOG_CONTEXT = WebService.class.getSimpleName();
        private final static String TOKEN_HEADER = "X-Auth-token";

        /**
         * Resolves the base directory for static resources.
         */
        private String getResourceBase() throws MalformedURLException {
            var resourceFile = getClass().getClassLoader().getResource("htdocs/index.html");
            return new URL(Objects.requireNonNull(resourceFile).getProtocol(), resourceFile.getHost(), resourceFile.getPath()
                    .substring(0, resourceFile.getPath().lastIndexOf("/")))
                    .toString();
        }

        /**
         * Configures connectors, servlets, and authentication hooks.
         */
        public HTTPServer() throws Exception {
            var config = context.getConfig();
            var connector = new ServerConnector(this);
            connector.setPort(config.getHttpPort());
            var host = config.getHost();
            if (host != null && !host.equals(ANY_HOST)) {
                connector.setHost(host);
            }

            this.addConnector(connector);
            var servletContext = new ServletContextHandler("/", ServletContextHandler.SESSIONS);

            servletContext.addServlet(JSONRPC, JSONRPCServlet.CONTEXT_PATH);
            var holder = servletContext.addServlet(DefaultServlet.class, "/");
            try {
                holder.setInitParameter("resourceBase", getResourceBase());
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
            this.setHandler(servletContext);
            start();

            JSONRPC.addRequestHandler((request, response, call) -> {
                var authManager = context.getServiceManager().getService(AuthManager.class);
                var authorized = authManager.getSessionAuthState(call.getContext().getSession());
                // Thread.sleep(100);//FIXME remove! debug only

                //auth by token
                if (!authorized) {
                    var headerToken = request.getHeader(TOKEN_HEADER);
                    if (headerToken != null) {
                        authorized = authManager.validateToken(headerToken);
                        authManager.setSessionAuthState(call.getContext().getSession(), authorized);
                    }
                }


                var isProtectedAccess = call.getMethod().getAnnotation(ProtectedMethod.class);
                if (isProtectedAccess != null) {
                    if (!authorized) throw new SecurityException("Forbidden");
                }
            });


            for (var handlerClass : RPCHandlerTypes) {
                var instance = RPC.instantiate(handlerClass, context);
                //noinspection unchecked
                JSONRPC.addTargetInstance((Class<? super RPC>) handlerClass, instance);
            }


            JSONRPC.getErrorHandler().add(throwable -> {
                SystemLogger.error("JRPC Request " +
                        (throwable.getRequestData() == null ? "" : throwable.getRequestData().toString()) +
                        " has failed with error", LOG_CONTEXT, throwable.getError());
            });

        }
    }


    private final static Set<Class<? extends RPC>> RPCHandlerTypes = Set.of(Auth.class, NetworkManager.class, SubscriptionManager.class, System.class);

    @Getter
    private final JSONRPCServlet JSONRPC = new JSONRPCServlet();
    private final HTTPServer httpServer;

    /**
     * Starts the web service and publishes the JSON-RPC servlet.
     */
    public WebService(Context context) {
        super(context);

        try {
            httpServer = new HTTPServer();
        } catch (Exception e) {
            throw new RuntimeException("Unable to start web server", e);
        }
    }

    private final static String CTX = WebService.class.getSimpleName();
}
