package dmo.fs.spa.router;

import com.google.cloud.firestore.Firestore;
import dmo.fs.db.wsnext.DbConfiguration;
import dmo.fs.quarkus.Server;
import dmo.fs.spa.SpaApplication;
import dmo.fs.spa.db.SpaDatabase;
import dmo.fs.spa.db.SpaDbConfiguration;
import dmo.fs.spa.db.neo4j.SpaNeo4j;
import dmo.fs.spa.db.reactive.SpaDatabaseReactive;
import dmo.fs.spa.utils.SpaLogin;
import dmo.fs.spa.utils.SpaUtil;
import dmo.fs.utils.ColorUtilConstants;
import dmo.fs.utils.DodexUtil;
import io.smallrye.mutiny.Uni;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;
import io.vertx.mutiny.core.Promise;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.core.http.HttpServerResponse;
import io.vertx.mutiny.ext.web.Route;
import io.vertx.mutiny.ext.web.Router;
import io.vertx.mutiny.ext.web.handler.CorsHandler;
import io.vertx.mutiny.ext.web.handler.SessionHandler;
import io.vertx.mutiny.ext.web.sstore.LocalSessionStore;
import io.vertx.mutiny.ext.web.sstore.SessionStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.Optional;
import java.util.concurrent.ExecutionException;

public class SpaRoutes {
    protected static final Logger logger = LoggerFactory.getLogger(SpaRoutes.class.getName());
    protected static final String FAILURE = "{\"status\":\"-99\"}";
    protected SpaDatabase spaDatabase;
    protected SpaDatabaseReactive spaDatabaseReactive;
    protected SpaNeo4j spaNeo4j;

    protected Vertx vertx = Server.getVertxMutiny();
    protected Router router;
    protected SessionStore sessionStore;
    protected Firestore firestore;

    public SpaRoutes(Router router, Promise<Router> routesPromise) {
        setSpaRoutes(router, routesPromise);
    }

    public SpaRoutes(Router router, Promise<Router> routesPromise, Firestore firestore) {
        this.firestore = firestore;
        setSpaRoutes(router, routesPromise);
    }

    protected void setSpaRoutes(Router router, Promise<Router> routesPromise) {
        this.router = router;
        sessionStore = LocalSessionStore.create(vertx);

        if (SpaDbConfiguration.isUsingCassandra() || DbConfiguration.isUsingFirebase()
          || SpaDbConfiguration.isUsingNeo4j()) {
            if (SpaDbConfiguration.isUsingNeo4j()) {
                spaNeo4j = SpaDbConfiguration.getSpaDb();
                spaNeo4j.databaseSetup();
            }
            setGetLoginRoute();
            setPutLoginRoute();
            setLogoutRoute();
            setUnregisterLoginRoute();
            routesPromise.complete(router);
        } else {
            String defaultDb;
            try {
                defaultDb = new DodexUtil().getDefaultDb();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if ("h2".equals(defaultDb) || "sqlite3".equals(defaultDb)) {
                try {
                    spaDatabaseReactive = SpaDbConfiguration.getSpaDb();
                    if (spaDatabaseReactive != null) {
                        spaDatabaseReactive.databaseSetup().onSuccess(none -> {
                            setGetLoginRoute();
                            setPutLoginRoute();
                            setLogoutRoute();
                            setUnregisterLoginRoute();
                            routesPromise.complete(router);
                        });
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else {
                spaDatabase = SpaDbConfiguration.getSpaDb();
                if (spaDatabase != null) {
                    spaDatabase.databaseSetup().onSuccess(none -> {
                        setGetLoginRoute();
                        setPutLoginRoute();
                        setLogoutRoute();
                        setUnregisterLoginRoute();
                        routesPromise.complete(router);
                    }).onFailure(err -> {
                        logger.info("Database setup Failed: {}", err.getMessage());
                    });
                }
            }
        }
    }

    public void setGetLoginRoute() {
        SessionHandler sessionHandler = SessionHandler.create(sessionStore);

        Route route = router.route(HttpMethod.POST, "/userlogin"); // .handler(sessionHandler);
        if ("dev".equals(DodexUtil.getEnv())) {
            route.handler(CorsHandler.create().allowedMethod(HttpMethod.POST));
        }

        route.failureHandler(routingContext -> {
            HttpServerResponse response = routingContext.response();
            if (response.getStatusCode() != 200) {
                logger.info("Login Post Error: {} -- {} -- {}", response.headers(), response.getStatusCode(), response.getStatusMessage());
            }
        });

        route.handler(routingContext -> routingContext.request().bodyHandler(bodyHandler -> {
            SpaApplication spaApplication = null;

            try {
                spaApplication = new SpaApplication();
                if (spaDatabase != null) {
                    spaApplication.setDatabase(spaDatabase);
                }
            } catch (InterruptedException | IOException | SQLException e1) {
                e1.printStackTrace();
            }
            String body = bodyHandler.toString("UTF-8");
            String bodyDecoded;

            bodyDecoded = URLDecoder.decode(body, StandardCharsets.UTF_8);

            routingContext.get("getlogin");
            routingContext.put("name", "getlogin");

            HttpServerResponse response = routingContext.response();
            final Optional<String> queryData = Optional.ofNullable(bodyDecoded);

            if (queryData.isPresent()) {
                response.putHeader("content-type", "application/json");

                Promise<SpaLogin> promise;
                try {
                    assert spaApplication != null;
                    promise = spaApplication.getLogin(bodyDecoded);
                    promise.future().onItem().call(result -> {
                        if (result.getId() == null) {
                            result.setId(0L);
                        }

//                        session.put("login", new JsonObject(result.getMap()));
                        response.send(new JsonObject(result.getMap()).encode()).subscribeAsCompletionStage().isDone();
                        routingContext.request().resume();
                        return Uni.createFrom().item(result);
                    }).onFailure().invoke(failed -> {
                        failed.printStackTrace();
                        logger.error(String.format("%s%s%s%s", ColorUtilConstants.RED_BOLD_BRIGHT,
                          "Get Login Failed: ", failed, ColorUtilConstants.RESET));
                        response.end(FAILURE).subscribeAsCompletionStage().isDone();
                    }).subscribeAsCompletionStage().isDone();
                } catch (InterruptedException | ExecutionException | SQLException e) {
                    e.printStackTrace();
                }
            } else {
                routingContext.response().setStatusCode(500).end("Somethings wrong - Post")
                  .subscribeAsCompletionStage().isDone();
            }
        }));
        if (!DbConfiguration.isUsingPostgres() &&
          !DbConfiguration.isUsingMariadb() &&
          !DbConfiguration.isUsingIbmDB2()) {
            route.handler(sessionHandler);
        }
    }

    public void setPutLoginRoute() {
        Route route = router.route(HttpMethod.PUT, "/userlogin"); // .handler(sessionHandler);

        if ("dev".equals(DodexUtil.getEnv())) {
            route.handler(CorsHandler.create().allowedMethod(HttpMethod.PUT));
        }

        route.failureHandler(routingContext -> {
            HttpServerResponse response = routingContext.response();
            if (response.getStatusCode() != 200) {
                logger.info("Login PUT Error: {} -- {} -- {}", response.headers(), response.getStatusCode(), response.getStatusMessage());
            }
        });

        route.handler(routingContext -> routingContext.request().bodyHandler(bodyHandler -> {
            SpaApplication spaApplication = null;
            try {
                spaApplication = new SpaApplication();
                if (spaDatabase != null) {
                    spaApplication.setDatabase(spaDatabase);
                }
            } catch (InterruptedException | IOException | SQLException e1) {
                e1.printStackTrace();
            }
            final String body = bodyHandler.toString("UTF-8");
            try {
                final String bodyDecoded = URLDecoder.decode(body, StandardCharsets.UTF_8);

                routingContext.put("name", "putlogin");

                HttpServerResponse response = routingContext.response();

                final Optional<String> queryData = Optional.ofNullable(bodyDecoded);
                if (queryData.isPresent()) {
                    if (!response.headWritten()) {
                        response.putHeader("content-type", "application/json");
                    }
                    SpaLogin spaLogin = SpaUtil.createSpaLogin();

                    SpaUtil.parseBody(bodyDecoded, spaLogin);
                    JsonObject jsonObject = new JsonObject(spaLogin.getMap());
                    assert spaApplication != null;
                    Promise<SpaLogin> promiseLogin = spaApplication.getLogin(jsonObject.encode());

                    promiseLogin.future().onItem().call(result -> {
                        if ("0".equals(result.getStatus())) {
                            result.setStatus("-2");
                            response.end(new JsonObject(result.getMap()).encode())
                              .subscribeAsCompletionStage().isDone();
                        } else {
                            SpaApplication spaApplicationAdd = null;
                            try {
                                spaApplicationAdd = new SpaApplication();
                                if (spaDatabase != null) {
                                    spaApplicationAdd.setDatabase(spaDatabase);
                                }
                            } catch (InterruptedException | IOException | SQLException e) {
                                e.printStackTrace();
                            }
                            Promise<SpaLogin> promise;
                            try {
                                assert spaApplicationAdd != null;
                                promise = spaApplicationAdd.addLogin(bodyDecoded);

                                promise.future().onItem().call(result2 -> {
                                    response.end(new JsonObject(result2.getMap()).encode())
                                      .subscribeAsCompletionStage().isDone();
                                    return Uni.createFrom().item(result2);
                                }).onFailure().invoke(failed -> {
                                    logger.error(String.format("%s%s%s%s", ColorUtilConstants.RED_BOLD_BRIGHT,
                                      "Add Login failed...: ", failed.getMessage(),
                                      ColorUtilConstants.RESET));
                                    response.end(FAILURE).subscribeAsCompletionStage().isDone();
                                }).subscribeAsCompletionStage().isDone();
                            } catch (InterruptedException | ExecutionException e) {
                                e.printStackTrace();
                            }
                        }
                        return Uni.createFrom().item(result);
                    }).onFailure().invoke(failed -> {
                        failed.printStackTrace();
                        logger.error(String.format("%s%s%s%s", ColorUtilConstants.RED_BOLD_BRIGHT,
                          "Check Login failed...: ", failed, ColorUtilConstants.RESET));
                        response.end(FAILURE).subscribeAsCompletionStage().isDone();
                    }).subscribeAsCompletionStage().isDone();
                } else {
                    routingContext.response().setStatusCode(500).end("Somethings wrong - Put")
                      .subscribeAsCompletionStage().isDone();
                }
            } catch (InterruptedException | ExecutionException | SQLException e) {
                e.printStackTrace();
            }
        }));
    }

    public void setLogoutRoute() {
        Route route = router.route(HttpMethod.DELETE, "/userlogin"); // .handler(sessionHandler);
        if ("dev".equals(DodexUtil.getEnv())) {
            route.handler(CorsHandler.create().allowedMethod(HttpMethod.DELETE));
        }

        route.failureHandler(routingContext -> {
            HttpServerResponse response = routingContext.response();
            if (response.getStatusCode() != 200) {
                logger.info("Login DELETE Error: {} -- {} -- {}", response.headers(), response.getStatusCode(), response.getStatusMessage());
            }
        });

        route.handler(routingContext -> {
            String data = null;

            routingContext.put("name", "getlogin");
            String status = "0";

            HttpServerResponse response = routingContext.response();
            response.putHeader("content-type", "application/json");

            final Optional<String> queryData = Optional.ofNullable(routingContext.request().query());
            if (queryData.isPresent()) {
                try {
                    data = String.join("", "{\"status\":\"", status, "\"}");
                } catch (Exception e) {
                    logger.error(String.join("", ColorUtilConstants.RED_BOLD_BRIGHT,
                      "Context Configuration failed...: ", e.getMessage(), ColorUtilConstants.RESET));
                }
            }

            if (data == null) {
                data = FAILURE;
            }
            response.end(data).subscribeAsCompletionStage().isDone();
        });
    }

    public void setUnregisterLoginRoute() {
        SessionHandler sessionHandler = SessionHandler.create(sessionStore);
        Route route = router.route(HttpMethod.DELETE, "/userlogin/unregister").handler(sessionHandler);

        if ("dev".equals(DodexUtil.getEnv())) {
            route.handler(CorsHandler.create().allowedMethod(HttpMethod.DELETE));
        }

        route.failureHandler(routingContext -> {
            HttpServerResponse response = routingContext.response();
            if (response.getStatusCode() != 200) {
                logger.info("Login Unregister Error: {} -- {} -- {}", response.headers(), response.getStatusCode(), response.getStatusMessage());
            }
        });

        route.handler(routingContext -> {
            SpaApplication spaApplication = null;
            try {
                spaApplication = new SpaApplication();
                if (spaDatabase != null) {
                    spaApplication.setDatabase(spaDatabase);
                }
            } catch (InterruptedException | IOException | SQLException e1) {
                e1.printStackTrace();
            }

            routingContext.put("name", "unregisterlogin");

            HttpServerResponse response = routingContext.response();

            final Optional<String> queryData = Optional.ofNullable(routingContext.request().query());
            if (queryData.isPresent()) {
                Promise<SpaLogin> promise;
                response.putHeader("content-type", "application/json");

                try {
                    assert spaApplication != null;
                    promise = spaApplication.unregisterLogin(URLDecoder.decode(queryData.get(), StandardCharsets.UTF_8));

                    promise.future().onItem().call(result -> {
//                        session.destroy();
                        response.end(new JsonObject(result.getMap()).encode()).subscribeAsCompletionStage().isDone();
                        return Uni.createFrom().item(result);
                    }).onFailure().invoke(failed -> {
                        logger.error(String.format("%s%s%s%s", ColorUtilConstants.RED_BOLD_BRIGHT,
                          "Unregister Login failed...: ", failed.getMessage(), ColorUtilConstants.RESET));
                        response.end(FAILURE).subscribeAsCompletionStage().isDone();
                    }).subscribeAsCompletionStage().isDone();
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            } else {
                routingContext.response().setStatusCode(500).end("Somethings wrong - Delete")
                  .subscribeAsCompletionStage().isDone();
            }
        });
    }

    public Router getRouter() {
        return router;
    }
}
