package su.xserver.iikocon.iiko;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import su.xserver.iikocon.handler.AdminHandler;
import su.xserver.iikocon.handler.AuthHandler;
import su.xserver.iikocon.service.DataBaseService;
import su.xserver.iikocon.service.RestaurantService;

public class IikoHandler {

  private final Logger log = LoggerFactory.getLogger("[IikoHandler]");
  private final DataBaseService db;
  private final Vertx vertx;
  private final RestaurantService restaurantService;

  public IikoHandler(Vertx vertx, Router router, DataBaseService db, RestaurantService restaurantService, AuthHandler authHandler) {
    this.vertx = vertx;
    this.restaurantService = restaurantService;
    this.db = db;

    createTablesIfNotExist().onFailure(err -> {
      log.error("Failed to initialize database", err);
    });

    router.route("/api/reports/olap/*").handler(authHandler::requireAuth);
    router.get("/api/reports/olap/columns").handler(this::getColumns);
    router.delete("/api/reports/olap/columns/:fieldKey").handler(AdminHandler::requireAdmin).handler(this::deleteColumn);
    router.post("/api/reports/olap/initialize").handler(AdminHandler::requireAdmin).handler(this::postInitialize);
  }

  private void getColumns(RoutingContext ctx) {
    getAllFieldsWithReportAndTags()
      .onSuccess(ar -> ctx.response()
        .putHeader("Content-Type", "application/json")
        .end(ar.encodePrettily()))
      .onFailure(err -> ctx.response()
        .setStatusCode(500)
        .end(err.getMessage()));
  }

  public void deleteColumn(RoutingContext ctx) {
    String fieldKey = ctx.pathParam("fieldKey");
    String sql = "DELETE FROM iiko_fields_common WHERE field_key = ?";

    db.getPool().preparedQuery(sql)
      .execute(Tuple.of(fieldKey))
      .onSuccess(res -> {
        ctx.end();
      })
      .onFailure(err -> ctx.response().setStatusCode(500).end(err.getMessage()));
  }

  private void postInitialize(RoutingContext ctx) {
    JsonObject body = ctx.body().asJsonObject();

    if (body == null) {
      ctx.response()
        .setStatusCode(400)
        .end("Request body is missing or not a JSON object");
      return;
    }

    if (!body.containsKey("restaurantId") || body.getValue("restaurantId") == null) {
      ctx.response()
        .setStatusCode(400)
        .end("restaurantId is required");
      return;
    }

    Integer restaurantId;
    try {
      restaurantId = body.getInteger("restaurantId");
      if (restaurantId == null) {
        throw new IllegalArgumentException("restaurantId must be a number");
      }
    } catch (ClassCastException e) {
      ctx.response()
        .setStatusCode(400)
        .end("restaurantId must be a valid integer");
      return;
    }

    restaurantService.findById(restaurantId)
      .onSuccess(rest -> {

        IikoOlapClient iiko = new IikoOlapClient(vertx, rest);

        iiko.checkConnection()
          .onSuccess(ping -> clearTables()
            .onSuccess(data -> {
              IikoOlapColumnsImporter importer = new IikoOlapColumnsImporter(iiko, db);

              importer.fetchAndStoreAll()
                .onSuccess(res -> ctx.end("OK"))
                .onFailure(err -> ctx.response()
                  .setStatusCode(400)
                  .end(err.getMessage()));
            })
            .onFailure(err -> ctx.response()
              .setStatusCode(400)
              .end(err.getMessage())))
          .onFailure(err -> ctx.response().setStatusCode(400).end(err.getMessage()));
      })
      .onFailure(err -> ctx.response()
        .setStatusCode(400)
        .end(err.getMessage()));
  }

  public Future<JsonObject> getAllFieldsWithReportAndTags() {
    String sql = """
      SELECT
          fc.field_key,
          fc.field_key_normal,
          fc.name,
          fc.type,
          fc.type_normal,
          fc.aggregation_allowed,
          fc.grouping_allowed,
          fc.filtering_allowed,
          GROUP_CONCAT(DISTINCT rt.name ORDER BY rt.name SEPARATOR ',') AS report_names,
          GROUP_CONCAT(DISTINCT t.tag_name ORDER BY t.tag_name SEPARATOR ',') AS tag_names
      FROM iiko_fields_common fc
      LEFT JOIN iiko_report_type_fields rtf ON fc.field_id = rtf.field_id
      LEFT JOIN iiko_report_types rt ON rtf.report_type_id = rt.report_type_id
      LEFT JOIN iiko_field_tags ft ON fc.field_id = ft.field_id
      LEFT JOIN iiko_tags t ON ft.tag_id = t.tag_id
      GROUP BY fc.field_id
      ORDER BY fc.field_key
      """;

    return db.getPool().query(sql).execute()
      .map(rows -> {
        JsonArray columnsArray = new JsonArray();
        for (Row row : rows) {

          String reportNamesStr = row.getString("report_names");
          JsonArray reportTypes = new JsonArray();
          if (reportNamesStr != null && !reportNamesStr.isBlank()) {
            for (String name : reportNamesStr.split(",")) {
              reportTypes.add(name.trim());
            }
          }

          String tagNamesStr = row.getString("tag_names");
          JsonArray tags = new JsonArray();
          if (tagNamesStr != null && !tagNamesStr.isBlank()) {
            for (String tag : tagNamesStr.split(",")) {
              tags.add(tag.trim());
            }
          }

          JsonObject fieldObj = new JsonObject()
            .put("fieldKey", row.getString("field_key"))
            .put("fieldKeyNormal", row.getString("field_key_normal"))
            .put("reportTypes", reportTypes)
            .put("name", row.getString("name"))
            .put("type", row.getString("type"))
            .put("typeNormal", row.getString("type_normal"))
            .put("aggregationAllowed", row.getBoolean("aggregation_allowed"))
            .put("groupingAllowed", row.getBoolean("grouping_allowed"))
            .put("filteringAllowed", row.getBoolean("filtering_allowed"))
            .put("tags", tags);

          columnsArray.add(fieldObj);
        }
        return new JsonObject().put("columns", columnsArray);
      });
  }

  private Future<Void> createTablesIfNotExist() {
    String createReportTypes = """
      CREATE TABLE IF NOT EXISTS iiko_report_types (
          report_type_id INT AUTO_INCREMENT PRIMARY KEY,
          name VARCHAR(50) UNIQUE NOT NULL,
          description TEXT NOT NULL
      ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
      """;

    String createFieldsCommon = """
      CREATE TABLE IF NOT EXISTS iiko_fields_common (
          field_id INT AUTO_INCREMENT PRIMARY KEY,
          field_key VARCHAR(255) NOT NULL UNIQUE,
          field_key_normal VARCHAR(255) NOT NULL,
          name VARCHAR(255) NOT NULL,
          type VARCHAR(50) NOT NULL,
          type_normal VARCHAR(50) NOT NULL,
          aggregation_allowed BOOLEAN NOT NULL DEFAULT 0,
          grouping_allowed BOOLEAN NOT NULL DEFAULT 0,
          filtering_allowed BOOLEAN NOT NULL DEFAULT 0
      ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
      """;

    String createReportTypeFields = """
      CREATE TABLE IF NOT EXISTS iiko_report_type_fields (
          report_type_id INT NOT NULL,
          field_id INT NOT NULL,
          PRIMARY KEY (report_type_id, field_id),
          FOREIGN KEY (report_type_id) REFERENCES iiko_report_types(report_type_id) ON DELETE CASCADE,
          FOREIGN KEY (field_id) REFERENCES iiko_fields_common(field_id) ON DELETE CASCADE
      ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
      """;

    String createTags = """
      CREATE TABLE IF NOT EXISTS iiko_tags (
          tag_id INT AUTO_INCREMENT PRIMARY KEY,
          tag_name VARCHAR(100) UNIQUE NOT NULL
      ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
      """;

    String createFieldTags = """
      CREATE TABLE IF NOT EXISTS iiko_field_tags (
          field_id INT NOT NULL,
          tag_id INT NOT NULL,
          PRIMARY KEY (field_id, tag_id),
          FOREIGN KEY (field_id) REFERENCES iiko_fields_common(field_id) ON DELETE CASCADE,
          FOREIGN KEY (tag_id) REFERENCES iiko_tags(tag_id) ON DELETE CASCADE
      ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
      """;

    String idxKeyNormal = "CREATE INDEX IF NOT EXISTS idx_fields_common_key_normal ON iiko_fields_common(field_key_normal)";
    String idxFieldName = "CREATE INDEX IF NOT EXISTS idx_fields_common_name ON iiko_fields_common(name)";
    String idxFieldTagsTag = "CREATE INDEX IF NOT EXISTS idx_field_tags_tag_id ON iiko_field_tags(tag_id)";

    return db.getPool().query(createReportTypes).execute()
      .compose(v -> db.getPool().query(createFieldsCommon).execute())
      .compose(v -> db.getPool().query(createReportTypeFields).execute())
      .compose(v -> db.getPool().query(createTags).execute())
      .compose(v -> db.getPool().query(createFieldTags).execute())
      .compose(v -> db.getPool().query(idxKeyNormal).execute())
      .compose(v -> db.getPool().query(idxFieldName).execute())
      .compose(v -> db.getPool().query(idxFieldTagsTag).execute())
      .mapEmpty();
  }

  private Future<Void> clearTables() {
    String sql = """
      -- Отключаем проверку внешних ключей
      SET FOREIGN_KEY_CHECKS = 0;

      -- Удаляем данные из всех таблиц (порядок не важен при отключённой проверке)
      DELETE FROM iiko_field_tags;
      DELETE FROM iiko_report_type_fields;
      DELETE FROM iiko_fields_common;
      DELETE FROM iiko_tags;
      DELETE FROM iiko_report_types;

      -- Сбрасываем счётчики AUTO_INCREMENT (чтобы новые ID начинались с 1)
      ALTER TABLE iiko_fields_common AUTO_INCREMENT = 1;
      ALTER TABLE iiko_tags AUTO_INCREMENT = 1;
      ALTER TABLE iiko_report_types AUTO_INCREMENT = 1;

      -- Включаем проверку обратно
      SET FOREIGN_KEY_CHECKS = 1;
      """;
    return db.getPool().query(sql).execute().mapEmpty();
  }
}
