package emu.nebula.server;

import java.io.File;
import java.io.FileReader;

import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import emu.nebula.Config.HttpServerConfig;
import emu.nebula.GameConstants;
import emu.nebula.Nebula;
import emu.nebula.Nebula.ServerType;
import emu.nebula.proto.Pb.ClientDiff;
import emu.nebula.server.routes.*;
import emu.nebula.util.JsonUtils;
import io.javalin.Javalin;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.http.staticfiles.Location;
import lombok.Getter;

@Getter
public class HttpServer {
    private final Javalin app;
    private ServerType type;
    private boolean started;
    
    // Cached client diff
    private PatchList patchlist;
    private byte[] diff;
    
    public HttpServer(ServerType type) {
        this.type = type;
        this.app = Javalin.create(javalinConfig -> {
            var staticFilesDir = new File(Nebula.getConfig().getWebFilesDir());
            if (staticFilesDir.exists()) {
                javalinConfig.staticFiles.add(staticFilesDir.getPath(), Location.EXTERNAL);
            }
        });

        this.loadPatchList();
        this.addRoutes();
    }
    
    public HttpServerConfig getServerConfig() {
        return Nebula.getConfig().getHttpServer();
    }

    private HttpConnectionFactory getHttpFactory() {
        HttpConfiguration httpsConfig = new HttpConfiguration();
        SecureRequestCustomizer src = new SecureRequestCustomizer();
        src.setSniHostCheck(false);
        httpsConfig.addCustomizer(src);
        return new HttpConnectionFactory(httpsConfig);
    }

    private SslContextFactory.Server getSSLContextFactory() {
        SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
        sslContextFactory.setKeyStorePath(Nebula.getConfig().getKeystore().getPath());
        sslContextFactory.setKeyStorePassword(Nebula.getConfig().getKeystore().getPassword());
        sslContextFactory.setSniRequired(false);
        sslContextFactory.setRenegotiationAllowed(false);
        return sslContextFactory;
    }
    
    // Patch list
    
    public long getDataVersion() {
        return getPatchlist() != null ? getPatchlist().getVersion() : GameConstants.getDataVersion();
    }
    
    public synchronized void loadPatchList() {
        // Clear
        this.patchlist = null;
        this.diff = null;
        
        // Get file
        File file = new File(Nebula.getConfig().getPatchListPath());
        
        if (!file.exists()) {
            this.diff = ClientDiff.newInstance().toByteArray();
            return;
        }
        
        // Load
        try (FileReader reader = new FileReader(file)) {
            this.patchlist = JsonUtils.loadToClass(reader, PatchList.class);
            this.diff = patchlist.toProto().toByteArray();
        } catch (Exception e) {
            this.patchlist = null;
            this.diff = ClientDiff.newInstance().toByteArray();
        }
        
        if (this.patchlist != null) {
            Nebula.getLogger().info("Loaded patchlist (Data version: " + patchlist.getVersion() + ")");
        }
    }
    
    // Start server
    
    public void start() {
        if (this.started) return;
        this.started = true;

        // Http server
        if (getServerConfig().isUseSSL()) {
            ServerConnector sslConnector = new ServerConnector(getApp().jettyServer().server(), getSSLContextFactory(), getHttpFactory());
            sslConnector.setHost(getServerConfig().getBindAddress());
            sslConnector.setPort(getServerConfig().getBindPort());
            getApp().jettyServer().server().addConnector(sslConnector);

            getApp().start();
        } else {
            getApp().start(getServerConfig().getBindAddress(), getServerConfig().getBindPort());
        }

        // Done
        Nebula.getLogger().info("Http Server started on " + getServerConfig().getBindPort());
    }
    
    // Server endpoints

    private void addRoutes() {
        // Add routes
        if (this.getType().runLogin()) {
            this.addLoginServerRoutes();
        }
        
        if (this.getType().runGame()) {
            this.addGameServerRoutes();
        }
        
        // Exception handler
        getApp().exception(Exception.class, (e, c) -> {
            e.printStackTrace();
        });

        // Fallback handler
        getApp().error(404, this::notFoundHandler);
    }
    
    private void addLoginServerRoutes() {
        // https://en-sdk-api.yostarplat.com/
        getApp().post("/common/config", new CommonConfigHandler(this));
        getApp().post("/common/version", new HttpJsonResponse("{\"Code\":200,\"Data\":{\"Agreement\":[{\"Version\":\"0.1\",\"Type\":\"user_agreement\",\"Title\":\"用户协议\",\"Content\":\"\",\"Lang\":\"en\"},{\"Version\":\"0.1\",\"Type\":\"privacy_agreement\",\"Title\":\"隐私政策\",\"Content\":\"\",\"Lang\":\"en\"}],\"ErrorCode\":\"4.4\"},\"Msg\":\"OK\"}"));
        
        getApp().post("/user/detail", new UserLoginHandler());
        getApp().post("/user/set", new UserSetDataHandler());
        getApp().post("/user/login", new UserLoginHandler());
        getApp().post("/user/quick-login", new UserLoginHandler());
        
        getApp().post("/yostar/get-auth", new GetAuthHandler());
        getApp().post("/yostar/send-code", new HttpJsonResponse("{\"Code\":200,\"Data\":{},\"Msg\":\"OK\"}")); // Dummy handler
        
        // https://nova-static.stellasora.global/
        getApp().get("/meta/serverlist.html", new MetaServerlistHandler(this));
        getApp().get("/meta/win.html", new MetaWinHandler(this));
    }
    
    private void addGameServerRoutes() {
        getApp().post("/agent-zone-1/", new AgentZoneHandler());
    }
    
    private void notFoundHandler(Context ctx) {
        ctx.status(404);
        ctx.contentType(ContentType.APPLICATION_JSON);
        ctx.result("{}");
    }
}
