package su.xserver.iikocon.iiko;

import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import su.xserver.iikocon.service.DataBaseService;

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

public class IikoOlapColumnsImporter {

  private static final Logger log = LoggerFactory.getLogger("[IikoOlapColumnsImporter]");
  private final DataBaseService db;
  private final IikoOlapClient iikoOlapClient;
  private static final List<String> REPORT_TYPES = List.of("SALES", "TRANSACTIONS", "DELIVERIES");

  public IikoOlapColumnsImporter(IikoOlapClient iikoOlapClient, DataBaseService db) {
    this.iikoOlapClient = iikoOlapClient;
    this.db = db;
  }

  public Future<Void> fetchAndStoreAll() {
    return processAllReportTypesSequentially()
      .onSuccess(v -> log.info("All reports imported successfully"))
      .onFailure(err -> log.error("Import failed: {}", err.getMessage()));
  }

  private Future<Void> processAllReportTypesSequentially() {
    Future<Void> result = Future.succeededFuture();
    for (String reportType : REPORT_TYPES) {
      result = result.compose(v -> processOneReportType(reportType));
    }
    return result;
  }

  private Future<Void> processOneReportType(String reportType) {
    log.info("Processing report type: {}", reportType);
    return fetchColumnsFromIiko(reportType)
      .compose(columnsJson -> storeColumnsToDb(reportType, columnsJson));
  }

  private Future<JsonObject> fetchColumnsFromIiko(String reportType) {
    return iikoOlapClient.handleGet("/resto/api/v2/reports/olap/columns",
      new JsonObject().put("reportType", reportType));
  }

  private Future<Void> storeColumnsToDb(String reportType, JsonObject columns) {
    return getOrCreateReportType(reportType)
      .compose(reportTypeId -> {
        List<Future<Void>> fieldFutures = new ArrayList<>();
        for (String fieldKey : columns.fieldNames()) {
          JsonObject fieldDef = columns.getJsonObject(fieldKey);
          fieldFutures.add(storeSingleField(reportTypeId, fieldKey, fieldDef));
        }
        return Future.all(fieldFutures).mapEmpty();
      });
  }

  private Future<Integer> getOrCreateReportType(String reportType) {
    String selectSql = "SELECT report_type_id FROM iiko_report_types WHERE name = ?";
    return db.getPool().preparedQuery(selectSql).execute(Tuple.of(reportType))
      .compose(rows -> {
        if (rows.size() > 0) {
          return Future.succeededFuture(rows.iterator().next().getInteger("report_type_id"));
        } else {
          String insertSql = "INSERT INTO iiko_report_types (name, description) VALUES (?, ?)";
          return db.getPool().preparedQuery(insertSql)
            .execute(Tuple.of(reportType, "OLAP report type: " + reportType))
            .compose(ignored ->
              db.getPool().preparedQuery(selectSql).execute(Tuple.of(reportType))
                .map(rows2 -> rows2.iterator().next().getInteger("report_type_id"))
            );
        }
      });
  }

  /**
   * Сохранить одно поле (без дублирования).
   * Сначала получаем/создаём запись в iiko_fields_common,
   * затем связываем её с report_type_id через iiko_report_type_fields,
   * потом обрабатываем теги.
   */
  private Future<Void> storeSingleField(int reportTypeId, String fieldKey, JsonObject fieldDef) {
    String fieldKeyNormal = fieldKey.replace('.', '_');
    String name = fieldDef.getString("name");
    String originalType = fieldDef.getString("type");
    String typeNormal = normalizeType(originalType);
    boolean aggregationAllowed = fieldDef.getBoolean("aggregationAllowed", false);
    boolean groupingAllowed = fieldDef.getBoolean("groupingAllowed", false);
    boolean filteringAllowed = fieldDef.getBoolean("filteringAllowed", false);
    JsonArray tagsArray = fieldDef.getJsonArray("tags", new JsonArray());

    return getOrCreateCommonField(fieldKey, fieldKeyNormal, name, originalType, typeNormal,
      aggregationAllowed, groupingAllowed, filteringAllowed)
      .compose(fieldId -> linkFieldToReportType(reportTypeId, fieldId)
        .compose(v -> processTags(fieldId, tagsArray))
      );
  }

  /**
   * Найти или создать поле в iiko_fields_common (по уникальному field_key).
   */
  private Future<Integer> getOrCreateCommonField(String fieldKey, String fieldKeyNormal, String name,
                                                 String type, String typeNormal,
                                                 boolean aggAllowed, boolean groupAllowed, boolean filterAllowed) {
    String selectSql = "SELECT field_id FROM iiko_fields_common WHERE field_key = ?";
    return db.getPool().preparedQuery(selectSql).execute(Tuple.of(fieldKey))
      .compose(rows -> {
        if (rows.size() > 0) {
          return Future.succeededFuture(rows.iterator().next().getInteger("field_id"));
        } else {
          String insertSql = """
            INSERT INTO iiko_fields_common
            (field_key, field_key_normal, name, type, type_normal,
             aggregation_allowed, grouping_allowed, filtering_allowed)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """;
          return db.getPool().preparedQuery(insertSql)
            .execute(Tuple.of(fieldKey, fieldKeyNormal, name, type, typeNormal,
              aggAllowed, groupAllowed, filterAllowed))
            .compose(ignored ->
              db.getPool().preparedQuery(selectSql).execute(Tuple.of(fieldKey))
                .map(rows2 -> rows2.iterator().next().getInteger("field_id"))
            );
        }
      });
  }

  /**
   * Привязать поле к типу отчёта (если ещё не привязано).
   */
  private Future<Void> linkFieldToReportType(int reportTypeId, int fieldId) {
    String sql = "INSERT IGNORE INTO iiko_report_type_fields (report_type_id, field_id) VALUES (?, ?)";
    return db.getPool().preparedQuery(sql).execute(Tuple.of(reportTypeId, fieldId)).mapEmpty();
  }

  /**
   * Обработать теги поля (теги одинаковы для всех типов отчётов).
   */
  private Future<Void> processTags(int fieldId, JsonArray tags) {
    List<Future<Void>> tagFutures = new ArrayList<>();
    for (Object tagObj : tags) {
      String tagName = tagObj.toString();
      tagFutures.add(getOrCreateTag(tagName)
        .compose(tagId -> linkFieldTag(fieldId, tagId)));
    }
    return Future.all(tagFutures).mapEmpty();
  }

  private Future<Integer> getOrCreateTag(String tagName) {
    String selectSql = "SELECT tag_id FROM iiko_tags WHERE tag_name = ?";
    return db.getPool().preparedQuery(selectSql).execute(Tuple.of(tagName))
      .compose(rows -> {
        if (rows.size() > 0) {
          return Future.succeededFuture(rows.iterator().next().getInteger("tag_id"));
        } else {
          String insertSql = "INSERT IGNORE INTO iiko_tags (tag_name) VALUES (?)";
          return db.getPool().preparedQuery(insertSql).execute(Tuple.of(tagName))
            .compose(ignored ->
              db.getPool().preparedQuery(selectSql).execute(Tuple.of(tagName))
                .map(rows2 -> rows2.iterator().next().getInteger("tag_id"))
            );
        }
      });
  }

  private Future<Void> linkFieldTag(int fieldId, int tagId) {
    String sql = "INSERT IGNORE INTO iiko_field_tags (field_id, tag_id) VALUES (?, ?)";
    return db.getPool().preparedQuery(sql).execute(Tuple.of(fieldId, tagId)).mapEmpty();
  }

  private String normalizeType(String iikoType) {
    if (iikoType == null) return "string";
    return switch (iikoType) {
      case "ENUM", "STRING", "ID", "ID_STRING" -> "string";
      case "DATETIME" -> "datetime";
      case "INTEGER", "DURATION_IN_SECONDS" -> "integer";
      case "PERCENT", "AMOUNT", "MONEY" -> "decimal";
      default -> "string";
    };
  }
}
