package io.delphiplatform.api.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.UUID;

import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;

import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@ControllerAdvice
public class ApplicationExceptionHandler {

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<CustomErrorHolder> handleConverterErrors(MethodArgumentTypeMismatchException exception,
        ServletWebRequest webRequest) {
        Throwable cause = exception.getCause();
        String path = webRequest.getRequest().getServletPath();
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        int statusCode = httpStatus.value();
        String error = httpStatus.getReasonPhrase();
        String timestamp = LocalDateTime.now().toString();

        if (cause != null) {
            Throwable secondCause = cause.getCause();
            if (secondCause.getClass() == InvalidParameterException.class) {

                CustomErrorHolder customErrorHolder = new CustomErrorHolder(statusCode,
                    timestamp, error, secondCause.getMessage(),
                    path);
                log.warn("InvalidParameterException : ", secondCause);
                return new ResponseEntity<>(customErrorHolder, httpStatus);
            }
        }

        String message = "Invalid param value. Param: " + exception.getName() + ", value: " + exception.getValue();
        return new ResponseEntity<>(new CustomErrorHolder(statusCode,
            timestamp, error, message, path), httpStatus);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public void handleConstraintViolationException(ConstraintViolationException exception, ServletWebRequest webRequest)
        throws IOException {
        HttpServletResponse response = webRequest.getResponse();
        UUID uuid = UUID.randomUUID();
        if (response != null) {
            response.sendError(HttpStatus.BAD_REQUEST.value(), "Validation Constraint Violation. Log ID: " + uuid);
        }
        log.warn("Validation Constraint Violation." + uuid, exception);
    }

    @ExceptionHandler(InvalidParameterException.class)
    public void handleInvalidParameterException(InvalidParameterException exception, ServletWebRequest webRequest)
        throws IOException {
        HttpServletResponse response = webRequest.getResponse();
        if (response != null) {
            response.sendError(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
        }
        log.warn("InvalidParameterException occurred", exception);
    }

    @ExceptionHandler(EntityNotFoundException.class)
    public void handleConstraintViolationException(EntityNotFoundException exception, ServletWebRequest webRequest)
        throws IOException {
        HttpServletResponse response = webRequest.getResponse();
        UUID uuid = UUID.randomUUID();
        if (response != null) {
            response.sendError(HttpStatus.NOT_FOUND.value(), "Entity not found. Log ID: " + uuid);
        }
        log.warn("Entity not found. " + uuid, exception);
    }

    @ExceptionHandler(StatusRuntimeException.class)
    public ResponseEntity<CustomErrorHolder> handleStatusRuntimeException(StatusRuntimeException exception) {
        if (exception.getStatus() != null
            && Status.DEADLINE_EXCEEDED.getCode().equals(exception.getStatus().getCode())) {
            String path = ServletUriComponentsBuilder.fromCurrentRequest().replaceQuery(null).port(null).host(null)
                .scheme(null).build().getPath();

            CustomErrorHolder customErrorHolder = new CustomErrorHolder(
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                LocalDateTime.now().toString(),
                HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
                //if at some point API will be using grpc to connect to other services than BT - this message should be changed to more generic one
                "Bigtable DB query timed out",
                path
            );

            log.warn("StatusRuntimeException: ", exception);
            return new ResponseEntity<>(customErrorHolder, HttpStatus.INTERNAL_SERVER_ERROR);
        }

        throw exception;
    }

    @ExceptionHandler({Exception.class})
    public void defaultHandleException(Exception exception, ServletWebRequest webRequest) throws Exception {
        if (exception instanceof ResponseStatusException) {
            throw exception;
        }
        UUID errorUuid = UUID.randomUUID();
        log.error("Unhandled exception happened. ID: " + errorUuid, exception);
        HttpServletResponse response = webRequest.getResponse();
        if (response != null) {
            response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Error happened. Try again later. Log ID: " + errorUuid);
        }
    }

}
