package de.fete.adapter.in.web;

import de.fete.application.service.exception.EventAlreadyCancelledException;
import de.fete.application.service.exception.EventCancelledException;
import de.fete.application.service.exception.EventExpiredException;
import de.fete.application.service.exception.EventNotFoundException;
import de.fete.application.service.exception.ExpiryDateBeforeEventException;
import de.fete.application.service.exception.ExpiryDateInPastException;
import de.fete.application.service.exception.InvalidOrganizerTokenException;
import de.fete.application.service.exception.InvalidTimezoneException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

/** Global exception handler producing RFC 9457 Problem Details responses. */
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(
      MethodArgumentNotValidException ex,
      HttpHeaders headers,
      HttpStatusCode status,
      WebRequest request) {

    ProblemDetail problemDetail = ex.getBody();
    problemDetail.setTitle("Validation Failed");
    problemDetail.setType(URI.create("urn:problem-type:validation-error"));

    List<Map<String, String>> fieldErrors = ex.getBindingResult()
        .getFieldErrors()
        .stream()
        .map(fe -> Map.of(
            "field", fe.getField(),
            "message", fe.getDefaultMessage() != null ? fe.getDefaultMessage() : "invalid"
        ))
        .toList();

    problemDetail.setProperty("fieldErrors", fieldErrors);
    return handleExceptionInternal(ex, problemDetail, headers, status, request);
  }

  /** Handles expiry date before event date. */
  @ExceptionHandler(ExpiryDateBeforeEventException.class)
  public ResponseEntity<ProblemDetail> handleExpiryDateBeforeEvent(
      ExpiryDateBeforeEventException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.BAD_REQUEST, ex.getMessage());
    problemDetail.setTitle("Invalid Expiry Date");
    problemDetail.setType(URI.create("urn:problem-type:expiry-date-before-event"));
    return ResponseEntity.badRequest()
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Handles expiry date validation failures. */
  @ExceptionHandler(ExpiryDateInPastException.class)
  public ResponseEntity<ProblemDetail> handleExpiryDateInPast(
      ExpiryDateInPastException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.BAD_REQUEST, ex.getMessage());
    problemDetail.setTitle("Invalid Expiry Date");
    problemDetail.setType(URI.create("urn:problem-type:expiry-date-in-past"));
    return ResponseEntity.badRequest()
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Handles attempt to cancel an already cancelled event. */
  @ExceptionHandler(EventAlreadyCancelledException.class)
  public ResponseEntity<ProblemDetail> handleEventAlreadyCancelled(
      EventAlreadyCancelledException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.CONFLICT, ex.getMessage());
    problemDetail.setTitle("Event Already Cancelled");
    problemDetail.setType(URI.create("urn:problem-type:event-already-cancelled"));
    return ResponseEntity.status(HttpStatus.CONFLICT)
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Handles RSVP on cancelled event. */
  @ExceptionHandler(EventCancelledException.class)
  public ResponseEntity<ProblemDetail> handleEventCancelled(
      EventCancelledException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.CONFLICT, ex.getMessage());
    problemDetail.setTitle("Event Cancelled");
    problemDetail.setType(URI.create("urn:problem-type:event-cancelled"));
    return ResponseEntity.status(HttpStatus.CONFLICT)
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Handles RSVP on expired event. */
  @ExceptionHandler(EventExpiredException.class)
  public ResponseEntity<ProblemDetail> handleEventExpired(
      EventExpiredException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.CONFLICT, ex.getMessage());
    problemDetail.setTitle("Event Expired");
    problemDetail.setType(URI.create("urn:problem-type:event-expired"));
    return ResponseEntity.status(HttpStatus.CONFLICT)
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Handles invalid organizer token. */
  @ExceptionHandler(InvalidOrganizerTokenException.class)
  public ResponseEntity<ProblemDetail> handleInvalidOrganizerToken(
      InvalidOrganizerTokenException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.FORBIDDEN, ex.getMessage());
    problemDetail.setTitle("Forbidden");
    problemDetail.setType(URI.create("urn:problem-type:invalid-organizer-token"));
    return ResponseEntity.status(HttpStatus.FORBIDDEN)
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Handles event not found. */
  @ExceptionHandler(EventNotFoundException.class)
  public ResponseEntity<ProblemDetail> handleEventNotFound(
      EventNotFoundException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.NOT_FOUND, ex.getMessage());
    problemDetail.setTitle("Event Not Found");
    problemDetail.setType(URI.create("urn:problem-type:event-not-found"));
    return ResponseEntity.status(HttpStatus.NOT_FOUND)
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Handles invalid timezone. */
  @ExceptionHandler(InvalidTimezoneException.class)
  public ResponseEntity<ProblemDetail> handleInvalidTimezone(
      InvalidTimezoneException ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.BAD_REQUEST, ex.getMessage());
    problemDetail.setTitle("Invalid Timezone");
    problemDetail.setType(URI.create("urn:problem-type:invalid-timezone"));
    return ResponseEntity.badRequest()
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }

  /** Catches all unhandled exceptions. */
  @ExceptionHandler(Exception.class)
  public ResponseEntity<ProblemDetail> handleAll(Exception ex) {
    ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
        HttpStatus.INTERNAL_SERVER_ERROR,
        "An unexpected error occurred.");
    problemDetail.setTitle("Internal Server Error");
    return ResponseEntity.internalServerError()
        .contentType(MediaType.APPLICATION_PROBLEM_JSON)
        .body(problemDetail);
  }
}
