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

import cloud.nstart.terravault.ordinis.domain.DataScope;
import com.fasterxml.jackson.databind.JsonNode;
import io.hypersistence.utils.hibernate.type.json.JsonType;
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 org.hibernate.annotations.Type;

import java.time.OffsetDateTime;

/**
 * Outbox row для надёжной публикации в Kafka. Записывается в той же транзакции
 * что и dictionary_records change. {@code @Scheduled} poller (`ordinis-outbox`)
 * читает unpublished, отправляет в правильный scope-топик, помечает published_at.
 */
@Entity
@Table(name = "outbox_events")
public class OutboxEvent {

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

  @Column(name = "event_type", nullable = false, length = 64)
  private String eventType;

  @Column(name = "aggregate_type", nullable = false, length = 64)
  private String aggregateType;

  @Column(name = "aggregate_id", nullable = false, length = 256)
  private String aggregateId;

  @Column(name = "dictionary_name", length = 128)
  private String dictionaryName;

  @Enumerated(EnumType.STRING)
  @Column(name = "data_scope", nullable = false, length = 32)
  private DataScope dataScope;

  @Type(JsonType.class)
  @Column(name = "payload", nullable = false, columnDefinition = "jsonb")
  private JsonNode payload;

  @Column(name = "kafka_topic", nullable = false, length = 256)
  private String kafkaTopic;

  @Column(name = "kafka_key", nullable = false, length = 512)
  private String kafkaKey;

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

  @Column(name = "published_at")
  private OffsetDateTime publishedAt;

  @Column(name = "trace_id", length = 64)
  private String traceId;

  @Column(name = "span_id", length = 64)
  private String spanId;

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

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

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

  protected OutboxEvent() {}

  public OutboxEvent(
      String eventType,
      String aggregateType,
      String aggregateId,
      DataScope dataScope,
      JsonNode payload,
      String kafkaTopic,
      String kafkaKey) {
    this.eventType = eventType;
    this.aggregateType = aggregateType;
    this.aggregateId = aggregateId;
    this.dataScope = dataScope;
    this.payload = payload;
    this.kafkaTopic = kafkaTopic;
    this.kafkaKey = kafkaKey;
    this.createdAt = OffsetDateTime.now();
  }

  public void markPublished() {
    this.publishedAt = OffsetDateTime.now();
    this.lastError = null;
  }

  public void markFailed(String error) {
    this.attemptCount = (this.attemptCount == null ? 0 : this.attemptCount) + 1;
    this.lastError = error;
  }

  public void markDlq(String error) {
    this.lastError = error;
    this.dlqAt = OffsetDateTime.now();
  }

  public boolean isDlq() {
    return dlqAt != null;
  }

  public Long getId() { return id; }
  public String getEventType() { return eventType; }
  public String getAggregateType() { return aggregateType; }
  public String getAggregateId() { return aggregateId; }
  public String getDictionaryName() { return dictionaryName; }
  public DataScope getDataScope() { return dataScope; }
  public JsonNode getPayload() { return payload; }
  public String getKafkaTopic() { return kafkaTopic; }
  public String getKafkaKey() { return kafkaKey; }
  public OffsetDateTime getCreatedAt() { return createdAt; }
  public OffsetDateTime getPublishedAt() { return publishedAt; }
  public String getTraceId() { return traceId; }
  public String getSpanId() { return spanId; }
  public Integer getAttemptCount() { return attemptCount; }
  public String getLastError() { return lastError; }
  public OffsetDateTime getDlqAt() { return dlqAt; }

  public void setDictionaryName(String v) { this.dictionaryName = v; }
  public void setTraceId(String v) { this.traceId = v; }
  public void setSpanId(String v) { this.spanId = v; }
}
