package cloud.nstart.terravault.ordinis.domain.record;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * Native SQL для подсчёта orphan FK references — записи где
 * {@code data->>source_field} не находится в active records target dictionary
 * по {@code data->>target_field}.
 *
 * <p>Используется {@code OrphanReferenceScanner} раз в час для метрики
 * {@code nsi_orphan_references_total}. Отдельный component (не repository
 * метод) потому что используем JdbcTemplate с dynamic field names — JPA
 * не может взять {@code @Param} в JSONB key path.
 */
@Component
public class OrphanReferenceQuery {

  private final JdbcTemplate jdbc;

  @Autowired
  public OrphanReferenceQuery(JdbcTemplate jdbc) {
    this.jdbc = jdbc;
  }

  /**
   * Считает orphan references: active records в source dictionary где
   * {@code data->>source_field} НЕ присутствует среди active records
   * target dictionary {@code data->>target_field}. NULL-значения
   * пропускаются (optional fields, x-references не required).
   *
   * <p>Field names валидируются caller'ом (берутся из schema parser, не
   * из user input) — SQL injection невозможна. Используем native SQL
   * concat потому что JdbcTemplate не bind'ит JSONB key paths.
   *
   * @return count of orphan source records
   */
  public long countOrphans(
      UUID sourceDictionaryId,
      String sourceField,
      UUID targetDictionaryId,
      String targetField) {
    validateField(sourceField);
    validateField(targetField);

    String sql = """
        SELECT count(*) FROM dictionary_records src
        WHERE src.dictionary_id = ?
          AND src.valid_from <= now()
          AND src.valid_to > now()
          AND src.data ->> '%s' IS NOT NULL
          AND src.data ->> '%s' NOT IN (
            SELECT tgt.data ->> '%s'
            FROM dictionary_records tgt
            WHERE tgt.dictionary_id = ?
              AND tgt.valid_from <= now()
              AND tgt.valid_to > now()
              AND tgt.data ->> '%s' IS NOT NULL
          )
        """.formatted(sourceField, sourceField, targetField, targetField);

    Long count = jdbc.queryForObject(sql, Long.class, sourceDictionaryId, targetDictionaryId);
    return count == null ? 0 : count;
  }

  /**
   * SQL injection guard. Field name приходит из JSON Schema (мы парсим),
   * но мы не доверяем — extra paranoid check. Алlowед identifier rules.
   */
  private static void validateField(String field) {
    if (field == null || field.isBlank()) {
      throw new IllegalArgumentException("field name is empty");
    }
    if (!field.matches("^[a-zA-Z_][a-zA-Z0-9_]{0,127}$")) {
      throw new IllegalArgumentException(
          "field name has unsafe characters: " + field);
    }
  }
}
