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

import cloud.nstart.terravault.ordinis.validation.ValidationException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import io.micrometer.tracing.Tracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.time.format.DateTimeParseException;

import java.util.List;

@RestControllerAdvice
public class GlobalExceptionHandler {

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

  private final ObjectProvider<Tracer> tracerProvider;

  public GlobalExceptionHandler(ObjectProvider<Tracer> tracerProvider) {
    this.tracerProvider = tracerProvider;
  }

  @ExceptionHandler(ValidationException.class)
  public ResponseEntity<ApiErrorResponse> onValidation(ValidationException e) {
    List<ApiErrorResponse.FieldError> fields = e.getErrors().stream()
        .map(err -> new ApiErrorResponse.FieldError(err.path(), err.code(), err.message()))
        .toList();
    var body = ApiErrorResponse.withFieldErrors(
        422, "validation_failed", e.getMessage(), fields, currentTraceId());
    return ResponseEntity.unprocessableEntity().body(body);
  }

  @ExceptionHandler(OrdinisException.class)
  public ResponseEntity<ApiErrorResponse> onOrdinis(OrdinisException e) {
    var body = ApiErrorResponse.of(e.getStatus().value(), e.getCode(), e.getMessage(), currentTraceId());
    return ResponseEntity.status(e.getStatus()).body(body);
  }

  @ExceptionHandler(IllegalArgumentException.class)
  public ResponseEntity<ApiErrorResponse> onIllegalArgument(IllegalArgumentException e) {
    var body = ApiErrorResponse.of(400, "bad_request", e.getMessage(), currentTraceId());
    return ResponseEntity.badRequest().body(body);
  }

  /**
   * Невалидный JSON или несовместимый формат поля (напр. дата без времени для OffsetDateTime).
   * Возвращаем 400 с понятным сообщением вместо 500.
   */
  @ExceptionHandler(HttpMessageNotReadableException.class)
  public ResponseEntity<ApiErrorResponse> onMessageNotReadable(HttpMessageNotReadableException e) {
    String message = "Невалидный JSON в теле запроса";
    String code = "invalid_request_body";
    Throwable root = e.getRootCause() != null ? e.getRootCause() : e.getCause();
    if (root instanceof DateTimeParseException dtpe) {
      message = "Поле даты не распарсилось: '" + dtpe.getParsedString()
          + "'. Ожидается ISO-8601 datetime, например 2026-05-08T14:30:00+03:00.";
      code = "invalid_date_format";
    } else if (e.getCause() instanceof InvalidFormatException ife) {
      String field = ife.getPath().isEmpty()
          ? "?"
          : ife.getPath().get(ife.getPath().size() - 1).getFieldName();
      message = "Поле '" + field + "' не распарсилось. Ожидается " + ife.getTargetType().getSimpleName() + ".";
      code = "invalid_field_format";
    }
    var body = ApiErrorResponse.of(400, code, message, currentTraceId());
    return ResponseEntity.badRequest().body(body);
  }

  /**
   * Path не имеет зарегистрированного controller'а ИЛИ static resource'а.
   *
   * <p>Spring MVC throws:
   * <ul>
   *   <li>{@link NoResourceFoundException} — когда ResourceHttpRequestHandler не
   *       нашёл static resource. Поднимается на любой запрос вида
   *       {@code /api/v1/typo} к unrouted path, потому что dispatch fall'sback
   *       на static resource resolver.</li>
   *   <li>{@link NoHandlerFoundException} — когда
   *       {@code spring.mvc.throw-exception-if-no-handler-found=true} и нет
   *       static resource fallback. В нашем setup'е fallback включён, поэтому
   *       приоритетно срабатывает NoResourceFoundException, но handler покрывает
   *       обе ситуации для robustness.</li>
   * </ul>
   *
   * <p>До этого handler'а оба exception'а попадали в generic {@link Exception}
   * catch ниже → возвращали 500 internal_error. Эффекты:
   * <ul>
   *   <li>Disabled springdoc.swagger-ui (prod) — {@code /swagger-ui/index.html}
   *       вместо чистого 404 давал 500 с записью "Unhandled exception" в logs,
   *       спамил alerting.</li>
   *   <li>{@code GET /api/v1/typo} — клиенты получали 500 вместо 404,
   *       затрудняло debugging integrator'ам.</li>
   *   <li>Любой stale frontend bundle с переименованным endpoint в новой версии
   *       мог триггерить false-positive incident alerts.</li>
   * </ul>
   *
   * <p>Лог уровня DEBUG (не WARN/ERROR) — это ожидаемый 404, не аномалия.
   */
  @ExceptionHandler({NoResourceFoundException.class, NoHandlerFoundException.class})
  public ResponseEntity<ApiErrorResponse> onNotFound(Exception e) {
    log.debug("Resource not found: {}", e.getMessage());
    var body = ApiErrorResponse.of(404, "not_found", "Resource not found", currentTraceId());
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<ApiErrorResponse> onGeneric(Exception e) {
    log.error("Unhandled exception in REST API", e);
    var body = ApiErrorResponse.of(500, "internal_error", "Internal server error", currentTraceId());
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
  }

  private String currentTraceId() {
    Tracer tracer = tracerProvider.getIfAvailable();
    if (tracer == null) return null;
    var span = tracer.currentSpan();
    return span == null ? null : span.context().traceId();
  }
}
