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

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;

public interface WebhookDeliveryRepository extends JpaRepository<WebhookDelivery, Long> {

  /**
   * Due for retry: status IN (pending, retrying) AND next_attempt_at <= now().
   * Использует partial index {@code idx_webhook_deliveries_due}. Limit через
   * Pageable чтобы dispatcher не выгребал >1000 за тик.
   */
  @Query("""
      SELECT d FROM WebhookDelivery d
      WHERE d.status IN ('pending','retrying')
        AND d.nextAttemptAt <= :now
      ORDER BY d.nextAttemptAt ASC
      """)
  List<WebhookDelivery> findDue(@Param("now") OffsetDateTime now, Pageable pageable);

  /** DLQ листинг для admin UI. Partial index {@code idx_webhook_deliveries_dlq}. */
  Page<WebhookDelivery> findByStatus(WebhookDelivery.Status status, Pageable pageable);

  /** Метрика {@code nsi_webhook_dlq_count}. */
  long countByStatus(WebhookDelivery.Status status);

  /**
   * Idempotent insert (sub × event = uniq). На race между dispatcher tick'ами
   * вторая вставка попадёт в conflict, ON CONFLICT DO NOTHING. Native query
   * чтобы не тащить ON CONFLICT через JPA.
   */
  @Modifying
  @Query(value = """
      INSERT INTO webhook_deliveries (subscription_id, outbox_event_id, status,
                                      attempt_count, created_at, next_attempt_at)
      VALUES (:subscriptionId, :outboxEventId, 'pending', 0, now(), now())
      ON CONFLICT (subscription_id, outbox_event_id) DO NOTHING
      """, nativeQuery = true)
  int insertIfAbsent(
      @Param("subscriptionId") UUID subscriptionId,
      @Param("outboxEventId") Long outboxEventId);

  /** История доставок одного event'а — для drilldown в UI. */
  List<WebhookDelivery> findByOutboxEventIdOrderByCreatedAtDesc(Long outboxEventId);

  /** История доставок одной подписки. */
  Page<WebhookDelivery> findBySubscriptionIdOrderByCreatedAtDesc(
      UUID subscriptionId, Pageable pageable);

  /** История доставок одной подписки фильтрованная по status. */
  Page<WebhookDelivery> findBySubscriptionIdAndStatusOrderByCreatedAtDesc(
      UUID subscriptionId, WebhookDelivery.Status status, Pageable pageable);

  /**
   * Histogram aggregation для admin UI time-series chart. Группирует deliveries
   * подписки по time bucket (hour/day) × status, возвращает count'ы.
   *
   * <p>{@code bucketUnit} — Postgres date_trunc валидное значение:
   * {@code 'hour'} или {@code 'day'} (другие unit'ы service rejects).
   * Параметр инъектируется через native query (нельзя bind'ить interval label
   * как ?, см. PG docs «String literal type cast»).
   *
   * <p>Returns rows как {@code Object[]{OffsetDateTime bucketTs, String status,
   * Long count}}. Service aggregate'ит в structured DTO с пустыми bucket'ами
   * заполненными нулями (continuous timeline).
   *
   * <p>Index plan: {@code idx_webhook_deliveries_sub_created} (subscription_id,
   * created_at). Если будем фильтровать без сабскрипции (всё-про-всё) —
   * нужен будет ещё один index, но текущий use case admin-UI всегда per-sub.
   */
  @Query(value = """
      SELECT date_trunc(:bucketUnit, created_at) AS bucket_ts,
             status::text AS status,
             COUNT(*) AS cnt
      FROM webhook_deliveries
      WHERE subscription_id = :subscriptionId
        AND created_at >= :fromTs
        AND created_at <  :toTs
      GROUP BY bucket_ts, status
      ORDER BY bucket_ts ASC
      """, nativeQuery = true)
  List<Object[]> aggregateByBucket(
      @Param("subscriptionId") UUID subscriptionId,
      @Param("bucketUnit") String bucketUnit,
      @Param("fromTs") OffsetDateTime fromTs,
      @Param("toTs") OffsetDateTime toTs);
}
