package com.insa.app.network;

import com.google.gson.Gson;
import com.insa.app.Resources;
import com.insa.app.controller.Controller;
import com.insa.app.model.User;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.ObjectMapper;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import javafx.application.Platform;
import ro.pippo.core.Application;
import ro.pippo.core.Pippo;
import ro.pippo.core.WebServer;
import ro.pippo.core.route.RouteContext;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

public class NetworkService extends Application implements Runnable {
    Controller controller;
    private Optional<String> presenceServer;

    public NetworkService(Controller cntrl) {
        Unirest.setObjectMapper(new ObjectMapper() {
            private Gson gson = new Gson();
            public <T> T readValue(String s, Class<T> c) {
                try { return gson.fromJson(s, c); } catch(Exception e) { throw new RuntimeException(e); }
            }
            public String writeValue(Object o) {
                try{ return gson.toJson(o); } catch(Exception e) { throw new RuntimeException(e); }
            }
        });

        controller = cntrl;
    }

    @Override
    public void run() {
        Pippo pippo = new Pippo(this);
        WebServer server = pippo.getServer();
        server.getSettings().host("0.0.0.0").port(Resources.clientPort);
        pippo.start();
    }

    @Override
    protected void onInit() {
        GET("/user", routeContext -> handleGetUser(routeContext)); //return local user
        POST("/user/connect", routeContext -> handlePostUserConnect(routeContext));
        PUT("/user/connect", routeContext -> handlePostUserConnect(routeContext));
        DELETE("/users", routeContext -> handleDeleteUser(routeContext));
        POST("/message", routeContext -> handlePostMessage(routeContext)); //receive message
        POST("/notify", routeContext -> handlePostNotify(routeContext)); //receive other user changes
    }

    private void handleDeleteUser(RouteContext routeContext) {
        User user = routeContext.json().createEntityFromBody(User.class);
        controller.deleteUser(user);
    }

    private void handleGetUser(RouteContext routeContext) {
        try {
            String ip = routeContext.getRequest().getClientIp();
            routeContext.json().send(controller.getMe());
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, "" + e);
        }
    }

    private void handlePostUserConnect(RouteContext routeContext) {
        Logger.getAnonymousLogger().log(Level.INFO, "A new user is trying to connect");
        User user = routeContext.json().createEntityFromBody(User.class);
        Logger.getAnonymousLogger().log(Level.INFO, "Sending user information : " + controller.getMe().getName() + "@" + controller.getMe().getIpAddress());
        routeContext.json().send(controller.getMe());
        controller.addUser(user,true);
    }

    private void handlePostMessage(RouteContext routeContext) {
        NetworkMessage mess = routeContext.json().createEntityFromBody(NetworkMessage.class);

        if (mess == null) {
            getErrorHandler().handle(400, routeContext);
            return;
        } else if (!mess.getRecipientID().equals(controller.getMe().getId())) {
            getErrorHandler().handle(401, routeContext);
            return;
        }

        controller.addIncomingMessage(mess);
    }

    private void handlePostNotify(RouteContext routeContext) {
        User user = routeContext.json().createEntityFromBody(User.class);

        if (user == null) {
            getErrorHandler().handle(400, routeContext);
            return;
        }
        Logger.getAnonymousLogger().log(Level.INFO, "Received post notify with data : " + user);
        controller.updateUser(user);
    }

    public void sendMessage(NetworkMessage networkMessage, User recipient) {
        try {
            Unirest.post("http://" + recipient.getIpAddress() + "/message").header("accept", "application/json")
            .header("Content-Type", "application/json")
            .body(networkMessage)
            .asJson();
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE,  ""+e);
        }
    }

    public void sendNameChangeNotify(User newUser, User recipient) {
        try {
            Unirest.post("http://" + recipient.getIpAddress() + "/notify").header("accept", "application/json")
            .header("Content-Type", "application/json")
            .body(newUser)
            .asJson();
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, "" + e);
        }
    }

    public void sendConnectRequest(String ip) {
        try {
            HttpResponse<JsonNode> resp = Unirest.post("http://" + ip + "/user/connect").header("accept", "application/json")
            .header("Content-Type", "application/json")
            .body(controller.getMe())
            .asJson();
            User user = new Gson().fromJson(resp.getBody().getObject().toString(), User.class);
            controller.addUser(user,true);
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE,  "" +e);
        }
    }

    public User getDistantUser(String ip) throws UnirestException {
        try {
            return Unirest.get("http://" + ip + "/user").asObject(User.class).getBody();
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, ""+e);
            throw e;
        }
    }

    public List<User> getDistantUsers(String ip) {
        try {
            User[] users = Unirest.get("http://" + ip + "/users").asObject(User[].class).getBody();
            return Arrays.asList(users);
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, "" + e);
            return null;
        }
    }

    public void connectToPresenceServer(String ip) {
        presenceServer = Optional.of(ip);
        try {
            Unirest.put("http://" + ip + "/user")
                .header("Content-Type", "application/json")
                .body(controller.getMe())
                .asString();

            HttpResponse<JsonNode> resp = Unirest.get("http://" + ip + "/user")
                .header("Content-Type", "application/json")
                .header("accept", "application/json")
                .asJson();

            User[] users = new Gson().fromJson(resp.getBody().toString(), User[].class);

            for (User user : users) {
                if (!user.equals(controller.getMe())) {
                    controller.addUser(user,true);
                }
            }
        }
        catch (Exception e)
        {
            Logger.getAnonymousLogger().log(Level.SEVERE, "Failed to connect to presence server : " + e);
            Platform.exit();
        }
    }

    public void disconnectFromPresenceServer() {
        Logger.getAnonymousLogger().log(Level.INFO, "Disconnecting from presence server");
        if (presenceServer.isPresent()) {
            try {
                Unirest.delete("http://" + presenceServer.get() + "/user")
                    .header("Content-Type", "application/json")
                    .body(controller.getMe())
                    .asString();
            }
            catch (Exception e) {
                Logger.getAnonymousLogger().log(Level.SEVERE, "Failed to disconnect from presence server :" + e);
                Platform.exit();
            }
        }
    }

    public boolean isConnectedToPresenceServer() {
        return presenceServer.isPresent();
    }

    public void sendChangeNamePresenceServer() {
        try {
            Unirest.post("http://" + presenceServer.get() + "/user/name")
                .header("Content-Type", "application/json")
                .body(controller.getMe())
                .asString();
        }
        catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.SEVERE, "Failed to notify presence server from name change :" + e);
            Platform.exit();
        }
    }
}
