package de.avatic.lcc.controller;

import de.avatic.lcc.dto.error.ErrorDTO;
import de.avatic.lcc.dto.error.ErrorResponseDTO;
import de.avatic.lcc.util.exception.base.BadRequestException;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.internalerror.GeocodingException;
import de.avatic.lcc.util.exception.internalerror.PremiseValidationError;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.stream.Collectors;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ErrorResponseDTO> handleMalformedReqExceptions(
            HttpMessageNotReadableException exception) {

        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                "Malformed Request",
                exception.getMessage(),
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponseDTO> handleValidationExceptions(
            MethodArgumentNotValidException exception) {

        // Extract field-specific validation errors
        Map<String, String> fieldErrors = new HashMap<>();
        exception.getBindingResult().getFieldErrors().forEach(error -> {
            fieldErrors.put(error.getField(), error.getDefaultMessage());
        });

        // Create a readable error message
        String errorMessage = fieldErrors.isEmpty() ?
                "Validation failed" :
                "Validation failed for fields: " + String.join(", ", fieldErrors.keySet().stream().map(key -> key + " - " + fieldErrors.get(key)).toList());

        // You might want to create a custom ErrorDTO that can handle field errors
        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                "Validation failed",
                errorMessage,
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponseDTO> handleMethodArgumentNotValid(BadRequestException exception) {

        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                exception.getTitle(),
                exception.getMessage(),
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<ErrorResponseDTO> handleConstraintViolation(MethodArgumentTypeMismatchException exception) { //

        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                "Wrong Datatype",
                exception.getMessage(),
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(ForbiddenException.class)
    public ResponseEntity<ErrorResponseDTO> handleForbiddenException(ForbiddenException exception) { //

        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                "Forbidden Error",
                exception.getMessage(),
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.FORBIDDEN);
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ErrorResponseDTO> handleConstraintViolation(ConstraintViolationException exception) {

        String errorMessage = exception.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(", "));

        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                "Constraint Violation",
                errorMessage.isEmpty() ? "Validation constraints violated" : errorMessage,
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(PremiseValidationError.class)
    public ResponseEntity<ErrorResponseDTO> handlePremiseValidationException(PremiseValidationError exception) {
        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                "Validation error",
                exception.getMessage(),
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(GeocodingException.class)
    public ResponseEntity<ErrorResponseDTO> handleGeocodingException(GeocodingException exception) {
        ErrorDTO error = new ErrorDTO(
                exception.getClass().getName(),
                "Geocoding error",
                exception.getMessage(),
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(CompletionException.class)
    public ResponseEntity<ErrorResponseDTO> handleCompletionException(CompletionException exception) {
        Throwable cause = exception.getCause();

        if (cause instanceof PremiseValidationError) {
            return handlePremiseValidationException((PremiseValidationError) cause);
        } else if (cause instanceof GeocodingException) {
            return handleGeocodingException((GeocodingException) cause);
        } else if (cause instanceof BadRequestException) {
            return handleMethodArgumentNotValid((BadRequestException) cause);
        }

        return handleGenericException(exception);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponseDTO> handleGenericException(Exception exception) {

        Throwable cause = exception.getCause();

        ErrorDTO error = new ErrorDTO(
                cause != null ? cause.getClass().getName() : exception.getClass().getName(),
                "Internal Server Error",
                cause != null ? cause.getMessage() : exception.getMessage(),
                Arrays.asList(exception.getStackTrace())
        );

        return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}