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

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.domain.definition.DictionaryDefinition;
import cloud.nstart.terravault.ordinis.domain.record.DictionaryRecord;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.OffsetDateTime;
import java.util.Set;

/**
 * Запись в {@code audit_log} — юридически значимый лог. Источник истины для
 * compliance, отдельно от технических логов в Loki.
 *
 * <p>Захватывает: user (sub claim из JWT либо "anonymous"), scope, IP, UA,
 * trace_id из MDC (Micrometer Tracing). Вызывается из {@link
 * cloud.nstart.terravault.ordinis.restapi.service.DictionaryRecordService} в
 * той же транзакции что и change — at-least-once семантика.
 */
@Service
public class AuditLogger {

  private static final Logger log = LoggerFactory.getLogger(AuditLogger.class);

  private final AuditLogRepository repository;
  private final ScopeContext scopeContext;

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

  public void recordCreate(DictionaryDefinition d, DictionaryRecord r) {
    write("RECORD_CREATED", "CREATE", d, r, null, r.getData());
  }

  public void recordUpdate(
      DictionaryDefinition d, DictionaryRecord r, JsonNode payloadBefore) {
    write("RECORD_UPDATED", "UPDATE", d, r, payloadBefore, r.getData());
  }

  public void recordClose(DictionaryDefinition d, DictionaryRecord r, String reason) {
    write("RECORD_CLOSED", "CLOSE", d, r, r.getData(), null);
  }

  private void write(
      String eventType,
      String action,
      DictionaryDefinition d,
      DictionaryRecord r,
      JsonNode before,
      JsonNode after) {
    try {
      AuditLog entry = new AuditLog();
      entry.setEventTime(OffsetDateTime.now());
      entry.setEventType(eventType);
      entry.setAction(action);
      entry.setDictionaryId(d.getId());
      entry.setDictionaryName(d.getName());
      entry.setRecordId(r.getId());
      entry.setBusinessKey(r.getBusinessKey());
      entry.setPayloadBefore(before);
      entry.setPayloadAfter(after);

      User user = currentUser();
      entry.setUserId(user.id);
      entry.setUserScope(user.scope);

      RequestContext rc = currentRequest();
      entry.setIpAddress(rc.ip);
      entry.setUserAgent(rc.userAgent);
      entry.setRequestId(rc.requestId);
      entry.setTraceId(MDC.get("traceId"));

      repository.save(entry);
    } catch (Exception ex) {
      // Аудит не должен ломать основной flow — но логируем громко чтобы
      // алертилось. Compliance-кейс ловит на этой ошибке.
      log.error(
          "Failed to write audit_log entry: dictionary={} record_id={} action={}: {}",
          d.getName(), r.getId(), action, ex.getMessage(), ex);
    }
  }

  private User currentUser() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if (auth instanceof JwtAuthenticationToken jwt) {
      String sub = jwt.getToken().getSubject();
      if (sub == null || sub.isBlank()) {
        Object preferred = jwt.getToken().getClaim("preferred_username");
        sub = preferred == null ? jwt.getName() : String.valueOf(preferred);
      }
      Set<DataScope> scopes = scopeContext.currentScopes();
      DataScope effective = scopes.contains(DataScope.RESTRICTED) ? DataScope.RESTRICTED
          : scopes.contains(DataScope.INTERNAL) ? DataScope.INTERNAL
          : DataScope.PUBLIC;
      return new User(sub, effective);
    }
    return new User("anonymous", DataScope.PUBLIC);
  }

  private RequestContext currentRequest() {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    if (attrs instanceof ServletRequestAttributes sra) {
      HttpServletRequest req = sra.getRequest();
      String ip = headerOrRemote(req, "X-Forwarded-For");
      if (ip != null && ip.contains(",")) ip = ip.split(",")[0].trim();
      String requestId = req.getHeader("X-Request-Id");
      return new RequestContext(ip, req.getHeader("User-Agent"), requestId);
    }
    return new RequestContext(null, null, null);
  }

  private static String headerOrRemote(HttpServletRequest req, String header) {
    String v = req.getHeader(header);
    return (v == null || v.isBlank()) ? req.getRemoteAddr() : v;
  }

  private record User(String id, DataScope scope) {}
  private record RequestContext(String ip, String userAgent, String requestId) {}
}
