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

import cloud.nstart.terravault.ordinis.domain.webhook.WebhookDelivery;
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.Assertions.assertThat;

/**
 * State machine + backoff schedule тесты {@link WebhookDelivery}.
 */
class WebhookDeliveryLifecycleTest {

  private WebhookDelivery newDelivery() {
    return new WebhookDelivery(UUID.randomUUID(), 42L);
  }

  @Test
  void initialStateIsPending() {
    WebhookDelivery d = newDelivery();
    assertThat(d.getStatus()).isEqualTo(WebhookDelivery.Status.pending);
    assertThat(d.getAttemptCount()).isZero();
    assertThat(d.getDeliveredAt()).isNull();
    assertThat(d.getDlqAt()).isNull();
  }

  @Test
  void markDeliveredTransitionsToDelivered() {
    WebhookDelivery d = newDelivery();
    d.markDelivered(200);
    assertThat(d.getStatus()).isEqualTo(WebhookDelivery.Status.delivered);
    assertThat(d.getLastStatusCode()).isEqualTo(200);
    assertThat(d.getDeliveredAt()).isNotNull();
    assertThat(d.getLastError()).isNull();
  }

  @Test
  void markFailedTransitionsToRetryingAndIncrementsAttempt() {
    WebhookDelivery d = newDelivery();
    d.markFailed("Connection refused", null);
    assertThat(d.getStatus()).isEqualTo(WebhookDelivery.Status.retrying);
    assertThat(d.getAttemptCount()).isEqualTo(1);
    assertThat(d.getLastError()).isEqualTo("Connection refused");
    assertThat(d.getLastAttemptAt()).isNotNull();
    // Next attempt scheduled in future (backoff)
    assertThat(d.getNextAttemptAt()).isAfter(OffsetDateTime.now().minusMinutes(1));
  }

  @Test
  void backoffScheduleIncreasesWithAttempts() {
    WebhookDelivery d = newDelivery();

    d.markFailed("err1", 503);
    OffsetDateTime after1 = d.getNextAttemptAt();

    d.markFailed("err2", 503);
    OffsetDateTime after2 = d.getNextAttemptAt();

    d.markFailed("err3", 503);
    OffsetDateTime after3 = d.getNextAttemptAt();

    // Backoff растёт: 1m → 5m → 15m → 1h → 6h → 24h
    assertThat(after2).isAfter(after1);
    assertThat(after3).isAfter(after2);

    // Между attempt 1 и 2 разница примерно 5m - 1m = 4m → next позже
    long deltaSeconds = java.time.Duration.between(after1, after2).getSeconds();
    assertThat(deltaSeconds).isPositive();
  }

  @Test
  void reachingMaxAttemptsMovesToDlq() {
    WebhookDelivery d = newDelivery();
    // Симулируем 1000 попыток
    for (int i = 0; i < WebhookDelivery.MAX_ATTEMPTS; i++) {
      d.markFailed("persistent", 502);
    }
    assertThat(d.getAttemptCount()).isEqualTo(WebhookDelivery.MAX_ATTEMPTS);
    assertThat(d.getStatus()).isEqualTo(WebhookDelivery.Status.dlq);
    assertThat(d.getDlqAt()).isNotNull();
    assertThat(d.isDlq()).isTrue();
  }

  @Test
  void resetDlqRestoresRetryingStatus() {
    WebhookDelivery d = newDelivery();
    for (int i = 0; i < WebhookDelivery.MAX_ATTEMPTS; i++) {
      d.markFailed("persistent", 502);
    }
    assertThat(d.isDlq()).isTrue();

    d.resetDlq();
    assertThat(d.getStatus()).isEqualTo(WebhookDelivery.Status.retrying);
    assertThat(d.getDlqAt()).isNull();
    assertThat(d.getAttemptCount()).isZero();
    assertThat(d.isDlq()).isFalse();
  }

  @Test
  void deliveredAfterFailuresClearsError() {
    WebhookDelivery d = newDelivery();
    d.markFailed("transient", 503);
    d.markFailed("transient", 503);
    assertThat(d.getLastError()).isNotNull();

    d.markDelivered(200);
    assertThat(d.getLastError()).isNull();
    assertThat(d.getStatus()).isEqualTo(WebhookDelivery.Status.delivered);
    // attempt_count сохраняется (для observability — сколько раз retried)
    assertThat(d.getAttemptCount()).isEqualTo(2);
  }
}
