package su.xserver.iikocon;

import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.sstore.SessionStore;

import io.vertx.ext.web.sstore.redis.RedisSessionStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import su.xserver.iikocon.config.AppConfig;
import su.xserver.iikocon.handler.*;
import su.xserver.iikocon.iiko.IikoHandler;
import su.xserver.iikocon.iiko.IikoOlapClient;
import su.xserver.iikocon.service.*;

import java.util.ArrayList;
import java.util.List;

public class MainVerticle extends AbstractVerticle {

  private final Logger log = LoggerFactory.getLogger("[MainVerticle]");

  private DataBaseService db;
  private RedisService redis;
  private HttpServer httpServer;
  private AppConfig config;
  private SessionStore sessionStore;

  private UserService userService;
  private RestaurantService restaurantService;
  private ExternalDataBaseService externalDataBaseService;
  private SettingsService settingsService;

  @Override
  public void start(Promise<Void> startPromise) {

    ConfigStoreOptions classpathStore = new ConfigStoreOptions()
      .setType("file")
      .setFormat("json")
      .setConfig(new JsonObject().put("path", "config.json").put("hierarchical", true))
      .setOptional(false);

    ConfigRetrieverOptions options = new ConfigRetrieverOptions()
      .addStore(classpathStore);

    ConfigRetriever retriever = ConfigRetriever.create(vertx, options);

    retriever.getConfig()
      .onSuccess(cfg -> {
        config = AppConfig.from(cfg);

        db = new DataBaseService(vertx, config.database);
        redis = new RedisService(vertx, config.redis);

        userService = new UserService(db.getPool());
        restaurantService = new RestaurantService(db.getPool());
        settingsService = new SettingsService(db.getPool());
        externalDataBaseService = new ExternalDataBaseService(db.getPool(), vertx);

        userService.initDatabase().onFailure(err -> {
          log.error("Failed to initialize database", err);
          startPromise.fail(err);
        });
        restaurantService.initDatabase().onFailure(err -> {
          log.error("Failed to initialize database", err);
          startPromise.fail(err);
        });
        settingsService.initDatabase().onFailure(err -> {
          log.error("Failed to initialize database", err);
          startPromise.fail(err);
        });
        externalDataBaseService.initDatabase().onFailure(err -> {
          log.error("Failed to initialize database", err);
          startPromise.fail(err);
        });

        createRouterAndStartHttp(startPromise);

      })
      .onFailure(startPromise::fail);

  }

  private void createRouterAndStartHttp(Promise<Void> startPromise) {
    settingsService.get("session_timeout_minutes")
      .compose(timeoutStr -> {
        long timeoutMinutes = 60; // default
        if (timeoutStr != null && !timeoutStr.isEmpty()) {
          try {
            timeoutMinutes = Long.parseLong(timeoutStr);
          } catch (NumberFormatException ignored) {}
        }
        long timeoutMs = timeoutMinutes * 60 * 1000;

        sessionStore = RedisSessionStore.create(vertx, redis.getRedis());
        SessionHandler sessionHandler = SessionHandler.create(sessionStore)
          .setSessionCookieName("admin.session")
          .setCookieHttpOnlyFlag(true)
          .setCookieSecureFlag(false)
          .setSessionTimeout(timeoutMs);

        Router router = initRouter(sessionHandler);
        startHttp(router, startPromise);
        return Future.succeededFuture();
      })
      .onFailure(err -> {
        log.error("Failed to get session timeout", err);
        startPromise.fail(err);
      });
  }

  private Router initRouter(SessionHandler sessionHandler) {

    Router router = Router.router(vertx);
    router.route().handler(BodyHandler.create());
    router.route().handler(sessionHandler);

    SecurityHandler securityHandlers = new SecurityHandler(settingsService);

    // Обработчики безопасности
    router.route().handler(securityHandlers.hostValidator());
    router.route().handler(securityHandlers.proxyHeadersHandler());
    router.route().handler(securityHandlers.cspHeader());

    // CORS для разработки
    router.route().handler(ctx -> {
      ctx.response()
        .putHeader("Access-Control-Allow-Origin", "*")
        .putHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        .putHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
        .putHeader("Access-Control-Allow-Credentials", "true");

      if (ctx.request().method().name().equals("OPTIONS")) {
        ctx.response().end();
      } else {
        ctx.next();
      }
    });

    router.route().handler(ctx -> {
      long start = System.currentTimeMillis();
      String method = ctx.request().method().name();
      String path = ctx.request().path();
      final String remoteIp = ctx.get("realClientIp") != null ?
        ctx.get("realClientIp") :
        ctx.request().remoteAddress().host();
      ctx.addBodyEndHandler(v -> {
        long duration = System.currentTimeMillis() - start;
        log.info("{} {} - {} ms - {} - {}",
          method, path, duration, ctx.response().getStatusCode(), remoteIp);
      });
      ctx.next();
    });

    // ------ Раздаём Vue статику ------
    router.route("/assets/*").handler(StaticHandler.create("webroot/assets"));

    router.route("/favicon.ico").handler(ctx -> ctx.response().sendFile("webroot/favicon.ico"));

    // ------ SPA fallback: отдаём index.html на все не-API запросы ------
    router.route().handler(ctx -> {
      if (ctx.request().path().startsWith("/api")) {
        ctx.next();
      } else {
        ctx.response()
          .putHeader("Content-Type", "text/html")
          .sendFile("webroot/index.html");
      }
    });

    // Rate Limiter Handler
    RedisRateLimiter limiter = new RedisRateLimiter(
      redis.getRedis(), 60, 60_000
    );

    router.route().handler(limiter);

    // Health Checks
    HealthCheckService healthCheckService = new HealthCheckService(vertx, redis, db);
    healthCheckService.registerHealthCheck(router);

    // API маршруты
    AuthHandler authHandler = new AuthHandler(userService);
    SetupHandler setupHandler = new SetupHandler(userService);

    router.get("/api/status").handler(setupHandler::checkStatus);

    router.post("/api/setup").handler(setupHandler::handleSetup);

    router.post("/api/login").handler(authHandler::handleLogin);

    router.post("/api/logout").handler(authHandler::handleLogout);

    router.post("/api/register").handler(rc -> settingsService.get("enable_registration").onComplete(regCheck -> {
      if (regCheck.succeeded() && "false".equals(regCheck.result())) {
        rc.response().setStatusCode(403).end(new JsonObject().put("error", "Registration is disabled").encode());
        return;
      }
      JsonObject body = rc.body().asJsonObject();
      String login = body.getString("login");
      String email = body.getString("email");
      String password = body.getString("password");
      String ip = rc.request().remoteAddress().host();
      if (login == null || email == null || password == null) {
        rc.response().setStatusCode(400).end("Missing fields");
        return;
      }
      userService.createUser(login, email, password, ip)
        .onSuccess(v -> rc.response().setStatusCode(201).end(new JsonObject().put("success", true).encode()))
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    }));

    router.route("/api/profile").handler(authHandler::requireAuth);
    router.get("/api/profile").handler(rc -> {
      Integer userId = rc.session().get("userId");
      userService.getProfile(userId)
        .onSuccess(profile -> rc.response().putHeader("Content-Type", "application/json").end(profile.encode()))
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });
    router.put("/api/profile").handler(rc -> {
      Integer userId = rc.session().get("userId");
      JsonObject body = rc.body().asJsonObject();
      String email = body.getString("email");
      String password = body.getString("password");
      String language = body.getString("language");
      userService.updateProfile(userId, email, password, language)
        .onSuccess(v -> {
          if (language != null) rc.session().put("language", language);
          rc.response().end(new JsonObject().put("success", true).encode());
        })
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.route("/api/admin/*").handler(authHandler::requireAuth);
    router.get("/api/admin/users").handler(rc -> userService.getAllUsers().onComplete(ar -> {
      if (ar.succeeded()) {
        rc.response()
          .putHeader("Content-Type", "application/json")
          .end(ar.result().encode());
      } else {
        rc.response().setStatusCode(500).end(ar.cause().getMessage());
      }
    }));

    router.route("/api/admin/users*").handler(AdminHandler::requireAdmin);
    router.post("/api/admin/users").handler(rc -> {
      JsonObject body = rc.body().asJsonObject();
      String login = body.getString("login");
      String email = body.getString("email");
      String password = body.getString("password");
      String role = body.getString("role");
      String ip = rc.request().remoteAddress().host();
      if (login == null || email == null || password == null) {
        rc.response().setStatusCode(400).end("Missing login, email or password");
        return;
      }
      if (role == null || role.isEmpty()) role = "user";
      userService.createUser(login, email, password, ip, true, role)
        .onSuccess(v -> rc.response().setStatusCode(201).end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.put("/api/admin/users/:id").handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      JsonObject body = rc.body().asJsonObject();
      String login = body.getString("login");
      String email = body.getString("email");
      String password = body.getString("password");
      String role = body.getString("role");
      String ip = rc.request().remoteAddress().host();
      if (login == null || email == null) {
        rc.response().setStatusCode(400).end("Missing login or email");
        return;
      }
      userService.updateUser(id, login, email, password, ip, role)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.delete("/api/admin/users/:id").handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      Integer currentUserId = rc.session().get("userId");

      if (currentUserId != null && currentUserId == id) {
        rc.response().setStatusCode(403).end(new JsonObject()
          .put("error", "You cannot delete your own account")
          .encode());
        return;
      }

      userService.deleteUser(id)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.put("/api/admin/users/:id/activate").handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      boolean active = Boolean.parseBoolean(rc.queryParam("active").getFirst());
      Integer currentUserId = rc.session().get("userId");

      if (currentUserId != null && currentUserId == id) {
        rc.response().setStatusCode(403).end(new JsonObject().put("error", "You cannot deactivate yourself").encode());
        return;
      }

      userService.setActive(id, active)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.get("/api/admin/restaurants").handler(rc -> restaurantService.getAllRestaurants().onComplete(ar -> {
      if (ar.succeeded()) {
        rc.response()
          .putHeader("Content-Type", "application/json")
          .end(ar.result().encode());
      } else {
        rc.response().setStatusCode(500).end(ar.cause().getMessage());
      }
    }));

    router.get("/api/admin/restaurants/:id").handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      restaurantService.findById(id)
        .onSuccess(rest -> {
          if (rest == null) rc.response().setStatusCode(404).end();
          else rc.response().putHeader("Content-Type", "application/json").end(rest.encode());
        })
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.get("/api/admin/restaurants/:id/check").handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      restaurantService.findById(id)
        .onSuccess(rest -> {
          if (rest == null) {
            rc.response().setStatusCode(404).end();
          } else {
            IikoOlapClient iiko = new IikoOlapClient(vertx, rest);

            iiko.checkConnection()
              .onSuccess(res -> rc.response().putHeader("Content-Type", "application/json").end(res.encode()))
              .onFailure(err -> rc.response().putHeader("Content-Type", "application/json").end(
                new JsonObject().put("success", false).put("error", err.getMessage()).encode()));
          }
        })
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.post("/api/admin/restaurants").handler(rc -> {
      JsonObject body = rc.body().asJsonObject();
      String name = body.getString("name");
      String login = body.getString("login");
      String password = body.getString("password");
      String host = body.getString("host");
      boolean https = body.getBoolean("https", false);
      if (name == null || login == null || password == null || host == null) {
        rc.response().setStatusCode(400).end("Missing fields");
        return;
      }
      restaurantService.createRestaurant(name, login, password, host, https)
        .onSuccess(v -> rc.response().setStatusCode(201).end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.put("/api/admin/restaurants/:id").handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      JsonObject body = rc.body().asJsonObject();
      String name = body.getString("name");
      String login = body.getString("login");
      String password = body.getString("password");
      String host = body.getString("host");
      boolean https = body.getBoolean("https", false);
      if (name == null || login == null || host == null) {
        rc.response().setStatusCode(400).end("Missing required fields");
        return;
      }
      restaurantService.updateRestaurant(id, name, login, password, host, https)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.delete("/api/admin/restaurants/:id").handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      restaurantService.deleteRestaurant(id)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.get("/api/settings").handler(rc -> {
      settingsService.getPublicSettings()
        .onSuccess(settings -> rc.response().putHeader("Content-Type", "application/json").end(settings.encode()))
        .onFailure(err -> rc.response().setStatusCode(500).end());
    });

    router.route("/api/admin/settings*").handler(AdminHandler::requireAdmin);
    router.get("/api/admin/settings/meta").handler(rc -> {
      settingsService.getMetadata()
        .onSuccess(meta -> rc.response().putHeader("Content-Type", "application/json").end(meta.encode()))
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.get("/api/admin/settings").handler(rc -> {
      settingsService.getAllWithDefaults()
        .onSuccess(settings -> rc.response().putHeader("Content-Type", "application/json").end(settings.encode()))
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.put("/api/admin/settings").handler(rc -> {
      JsonObject body = rc.body().asJsonObject();
      List<Future<Void>> futures = new ArrayList<>(); // явно указываем тип Future<Void>
      body.forEach(entry -> futures.add(settingsService.set(entry.getKey(), entry.getValue().toString())));
      Future.all(futures)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    externalDataBaseService.handleRoute(router);

    new IikoHandler(vertx, router, db, restaurantService, authHandler);

    return router;
  }

  private void startHttp(Router router, Promise<Void> startPromise) {
    httpServer = vertx.createHttpServer();
    httpServer.requestHandler(router).listen(config.server.port, config.server.host)
      .onSuccess(server -> {
        log.info("HTTP server started on port {}", server.actualPort());
        startPromise.complete();
      })
      .onFailure(throwable -> {
        log.error(throwable.getMessage());
        startPromise.fail(throwable);
      });
  }

  @Override
  public void stop(Promise<Void> stopPromise) {
    this.httpServer.close()
      .onSuccess(server -> {
        log.info("Stop HTTP server");
        this.db.disconnect();
        this.redis.disconnect();
        this.vertx.close()
          .onSuccess(vertx -> {
            log.info("Stop Vert.x");
            stopPromise.complete();
          })
          .onFailure(throwable -> log.info(throwable.getMessage()));
      })
      .onFailure(throwable -> log.info(throwable.getMessage()));
  }
}
