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.audit.AuditLog;
import cloud.nstart.terravault.ordinis.domain.audit.AuditLogRepository;
import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.MediaType;
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.UUID;

/**
 * Admin endpoints для audit log. Read-only, фильтры опциональны.
 *
 * <ul>
 *   <li>{@code GET /admin/audit} — пагинированный поиск (требует
 *       INTERNAL+ scope — audit видит cross-tenant события).</li>
 *   <li>{@code GET /admin/audit/export.csv} — выгрузка по тем же фильтрам
 *       (без пагинации; cap 10 000 строк против OOM; требует RESTRICTED —
 *       bulk export — sensitive).</li>
 * </ul>
 */
@RestController
@RequestMapping("/api/v1/admin/audit")
public class AuditAdminController {

  private static final int MAX_EXPORT_ROWS = 10_000;

  private final AuditLogRepository repository;
  private final ScopeContext scopeContext;

  public AuditAdminController(AuditLogRepository repository, ScopeContext scopeContext) {
    this.repository = repository;
    this.scopeContext = scopeContext;
  }

  /** INTERNAL+ scope для просмотра audit log. PUBLIC отказывается. */
  private void requireInternal() {
    if (!scopeContext.canAccess(DataScope.INTERNAL)) {
      throw OrdinisException.forbidden(
          "audit_internal_required",
          "Audit log доступен только с INTERNAL или RESTRICTED scope.");
    }
  }

  /** RESTRICTED scope для bulk-операций (CSV экспорт). */
  private void requireRestricted() {
    if (!scopeContext.canAccess(DataScope.RESTRICTED)) {
      throw OrdinisException.forbidden(
          "audit_restricted_required",
          "Audit bulk export требует RESTRICTED scope (admin role).");
    }
  }

  @GetMapping
  public Page<AuditEntry> search(
      @RequestParam(required = false) String dictionaryName,
      @RequestParam(required = false) String userId,
      @RequestParam(required = false) String action,
      @RequestParam(required = false) String businessKey,
      @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime from,
      @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime to,
      @RequestParam(defaultValue = "0") int page,
      @RequestParam(defaultValue = "50") int size) {
    requireInternal();
    Page<AuditLog> result = repository.search(
        nullIfBlank(dictionaryName),
        nullIfBlank(userId),
        nullIfBlank(action),
        nullIfBlank(businessKey),
        from == null ? OffsetDateTime.parse("0001-01-01T00:00:00Z") : from,
        to == null ? OffsetDateTime.parse("9999-12-31T23:59:59Z") : to,
        PageRequest.of(page, Math.min(size, 200), Sort.by(Sort.Direction.DESC, "eventTime")));
    return result.map(AuditEntry::from);
  }

  @GetMapping(value = "/export.csv", produces = "text/csv")
  public org.springframework.http.ResponseEntity<String> exportCsv(
      @RequestParam(required = false) String dictionaryName,
      @RequestParam(required = false) String userId,
      @RequestParam(required = false) String action,
      @RequestParam(required = false) String businessKey,
      @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime from,
      @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime to) {
    requireRestricted();
    Page<AuditLog> result = repository.search(
        nullIfBlank(dictionaryName),
        nullIfBlank(userId),
        nullIfBlank(action),
        nullIfBlank(businessKey),
        from == null ? OffsetDateTime.parse("0001-01-01T00:00:00Z") : from,
        to == null ? OffsetDateTime.parse("9999-12-31T23:59:59Z") : to,
        PageRequest.of(0, MAX_EXPORT_ROWS, Sort.by(Sort.Direction.DESC, "eventTime")));

    StringBuilder csv = new StringBuilder(
        "id,event_time,event_type,action,user_id,user_scope,dictionary_name,business_key,record_id,trace_id,ip_address\n");
    for (AuditLog a : result.getContent()) {
      csv.append(a.getId()).append(',')
          .append(a.getEventTime()).append(',')
          .append(csvCell(a.getEventType())).append(',')
          .append(csvCell(a.getAction())).append(',')
          .append(csvCell(a.getUserId())).append(',')
          .append(a.getUserScope() == null ? "" : a.getUserScope().name()).append(',')
          .append(csvCell(a.getDictionaryName())).append(',')
          .append(csvCell(a.getBusinessKey())).append(',')
          .append(a.getRecordId() == null ? "" : a.getRecordId()).append(',')
          .append(csvCell(a.getTraceId())).append(',')
          .append(csvCell(a.getIpAddress()))
          .append('\n');
    }

    String filename = "audit-log-" + OffsetDateTime.now().toLocalDate() + ".csv";
    return org.springframework.http.ResponseEntity.ok()
        .header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
        .contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
        .body(csv.toString());
  }

  private static String nullIfBlank(String s) {
    return (s == null || s.isBlank()) ? null : s;
  }

  private static String csvCell(String s) {
    if (s == null) return "";
    if (s.indexOf(',') < 0 && s.indexOf('"') < 0 && s.indexOf('\n') < 0) return s;
    return "\"" + s.replace("\"", "\"\"") + "\"";
  }

  public record AuditEntry(
      Long id,
      OffsetDateTime eventTime,
      String eventType,
      String action,
      String userId,
      String userScope,
      String dictionaryName,
      UUID dictionaryId,
      UUID recordId,
      String businessKey,
      JsonNode payloadBefore,
      JsonNode payloadAfter,
      String traceId,
      String requestId,
      String ipAddress,
      String userAgent) {

    static AuditEntry from(AuditLog a) {
      return new AuditEntry(
          a.getId(),
          a.getEventTime(),
          a.getEventType(),
          a.getAction(),
          a.getUserId(),
          a.getUserScope() == null ? null : a.getUserScope().name(),
          a.getDictionaryName(),
          a.getDictionaryId(),
          a.getRecordId(),
          a.getBusinessKey(),
          a.getPayloadBefore(),
          a.getPayloadAfter(),
          a.getTraceId(),
          a.getRequestId(),
          a.getIpAddress(),
          a.getUserAgent());
    }
  }
}
