package cloud.nstart.terravault.ordinis.restapi.web;

import cloud.nstart.terravault.ordinis.auth.ScopeContext;
import cloud.nstart.terravault.ordinis.domain.DataScope;
import cloud.nstart.terravault.ordinis.domain.outbox.OutboxEvent;
import cloud.nstart.terravault.ordinis.domain.outbox.OutboxEventRepository;
import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * Admin endpoints для outbox observability. Только чтение — replay делается
 * вручную через SQL (UPDATE outbox_events SET dlq_at=NULL, attempt_count=0 WHERE id=...).
 * Авто-replay сделаем когда появится первый реальный кейс DLQ.
 *
 * <p><b>Access:</b>
 * <ul>
 *   <li>{@code /stats} — INTERNAL+ scope (просто counts, не sensitive).</li>
 *   <li>{@code /dlq} — RESTRICTED scope (lastError + payload могут содержать
 *       sensitive data из failed events).</li>
 * </ul>
 */
@RestController
@RequestMapping("/api/v1/admin/outbox")
public class OutboxAdminController {

  private final OutboxEventRepository repository;
  private final ScopeContext scopeContext;

  public OutboxAdminController(OutboxEventRepository repository, ScopeContext scopeContext) {
    this.repository = repository;
    this.scopeContext = scopeContext;
  }

  private void requireInternal() {
    if (!scopeContext.canAccess(DataScope.INTERNAL)) {
      throw OrdinisException.forbidden(
          "outbox_internal_required",
          "Outbox stats доступен только с INTERNAL или RESTRICTED scope.");
    }
  }

  private void requireRestricted() {
    if (!scopeContext.canAccess(DataScope.RESTRICTED)) {
      throw OrdinisException.forbidden(
          "outbox_restricted_required",
          "Outbox DLQ payloads требуют RESTRICTED scope (admin role).");
    }
  }

  @GetMapping("/stats")
  public Map<String, Long> stats() {
    requireInternal();
    return Map.of(
        "pending", repository.countPending(),
        "dlq", repository.countByDlqAtIsNotNull()
    );
  }

  @GetMapping("/dlq")
  public Page<DlqEntry> dlq(
      @RequestParam(defaultValue = "0") int page,
      @RequestParam(defaultValue = "50") int size) {
    requireRestricted();
    Page<OutboxEvent> events = repository.findByDlqAtIsNotNull(
        PageRequest.of(page, Math.min(size, 200), Sort.by(Sort.Direction.DESC, "dlqAt")));
    return events.map(DlqEntry::from);
  }

  public record DlqEntry(
      Long id,
      String eventType,
      String dictionaryName,
      String aggregateId,
      String kafkaTopic,
      Integer attemptCount,
      String lastError,
      OffsetDateTime createdAt,
      OffsetDateTime dlqAt) {

    static DlqEntry from(OutboxEvent e) {
      return new DlqEntry(
          e.getId(),
          e.getEventType(),
          e.getDictionaryName(),
          e.getAggregateId(),
          e.getKafkaTopic(),
          e.getAttemptCount(),
          e.getLastError(),
          e.getCreatedAt(),
          e.getDlqAt());
    }
  }
}
