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

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

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

/**
 * Webhook delivery — одна попытка доставки event'а подписчику. Один (subscription_id,
 * outbox_event_id) row на match (uniq index). Жизненный цикл:
 *
 * <pre>
 *   pending → retrying → delivered  (2xx response)
 *                     ↓
 *                     dlq           (после attempt_count >= 1000)
 * </pre>
 *
 * <p>Backoff schedule: 1m, 5m, 15m, 1h, 6h, 24h (exponential), capped 24h.
 * Computed в {@link #scheduleNextAttempt()}.
 */
@Entity
@Table(name = "webhook_deliveries")
public class WebhookDelivery {

  public enum Status {
    pending,
    retrying,
    delivered,
    dlq
  }

  /** Backoff schedule in seconds. attemptCount-1 индексирует. После — last value. */
  private static final long[] BACKOFF_SECONDS = {
      60L,        // 1 min
      5 * 60L,    // 5 min
      15 * 60L,   // 15 min
      60 * 60L,   // 1 h
      6 * 60 * 60L,  // 6 h
      24 * 60 * 60L  // 24 h
  };

  /** После этого attempt_count → DLQ flag. Совпадает с outbox cap. */
  public static final int MAX_ATTEMPTS = 1000;

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Long id;

  @Column(name = "subscription_id", nullable = false)
  private UUID subscriptionId;

  @Column(name = "outbox_event_id", nullable = false)
  private Long outboxEventId;

  @Enumerated(EnumType.STRING)
  @Column(name = "status", nullable = false, length = 16)
  private Status status = Status.pending;

  @Column(name = "attempt_count", nullable = false)
  private int attemptCount = 0;

  @Column(name = "created_at", nullable = false)
  private OffsetDateTime createdAt;

  @Column(name = "next_attempt_at", nullable = false)
  private OffsetDateTime nextAttemptAt;

  @Column(name = "last_attempt_at")
  private OffsetDateTime lastAttemptAt;

  @Column(name = "delivered_at")
  private OffsetDateTime deliveredAt;

  @Column(name = "last_error", columnDefinition = "TEXT")
  private String lastError;

  @Column(name = "last_status_code")
  private Integer lastStatusCode;

  @Column(name = "dlq_at")
  private OffsetDateTime dlqAt;

  protected WebhookDelivery() {}

  public WebhookDelivery(UUID subscriptionId, Long outboxEventId) {
    this.subscriptionId = subscriptionId;
    this.outboxEventId = outboxEventId;
    OffsetDateTime now = OffsetDateTime.now();
    this.createdAt = now;
    this.nextAttemptAt = now;
  }

  public void markDelivered(int statusCode) {
    this.status = Status.delivered;
    this.deliveredAt = OffsetDateTime.now();
    this.lastAttemptAt = this.deliveredAt;
    this.lastStatusCode = statusCode;
    this.lastError = null;
  }

  /** Отметить попытку как failed и запланировать retry, либо DLQ если cap. */
  public void markFailed(String error, Integer statusCode) {
    this.attemptCount++;
    this.lastAttemptAt = OffsetDateTime.now();
    this.lastError = error;
    this.lastStatusCode = statusCode;

    if (this.attemptCount >= MAX_ATTEMPTS) {
      this.status = Status.dlq;
      this.dlqAt = this.lastAttemptAt;
      return;
    }
    this.status = Status.retrying;
    scheduleNextAttempt();
  }

  /** Reset DLQ flag — поллер снова возьмётся (e.g. оператор fixed receiver URL). */
  public void resetDlq() {
    this.status = Status.retrying;
    this.dlqAt = null;
    this.attemptCount = 0;
    this.nextAttemptAt = OffsetDateTime.now();
  }

  private void scheduleNextAttempt() {
    int idx = Math.min(this.attemptCount - 1, BACKOFF_SECONDS.length - 1);
    long seconds = BACKOFF_SECONDS[idx];
    this.nextAttemptAt = OffsetDateTime.now().plusSeconds(seconds);
  }

  public Long getId() { return id; }
  public UUID getSubscriptionId() { return subscriptionId; }
  public Long getOutboxEventId() { return outboxEventId; }
  public Status getStatus() { return status; }
  public int getAttemptCount() { return attemptCount; }
  public OffsetDateTime getCreatedAt() { return createdAt; }
  public OffsetDateTime getNextAttemptAt() { return nextAttemptAt; }
  public OffsetDateTime getLastAttemptAt() { return lastAttemptAt; }
  public OffsetDateTime getDeliveredAt() { return deliveredAt; }
  public String getLastError() { return lastError; }
  public Integer getLastStatusCode() { return lastStatusCode; }
  public OffsetDateTime getDlqAt() { return dlqAt; }

  public boolean isDlq() { return status == Status.dlq; }
}
