package ru.yandex.qe.bus.exception;

import javax.annotation.Nonnull;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.NoContentException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;

import ru.yandex.qe.bus.ResponseUtils;

public class ServerExceptionMapper
        implements ExceptionMapper<Throwable> {

    private static final Logger LOG = LoggerFactory.getLogger(ServerExceptionMapper.class);

    @Nonnull
    @Override
    public Response toResponse(@Nonnull Throwable exception) {
        if (exception instanceof WebApplicationException
                && ((WebApplicationException) exception).getResponse().getStatus() == 404
                && exception.getCause() != null
                && exception.getCause().getClass().equals(IllegalArgumentException.class)) {
            // special case: cxf wraps IllegalArgumentException to WebApplicationException, setting 404 status
            // and hiding original message in InjectionUtils::evaluateFactoryMethods
            return handleCxfParamsException((WebApplicationException) exception);
        } else if (exception instanceof WebApplicationException) {
            return handleWebApplicationException((WebApplicationException) exception);
        } else if (exception instanceof AccessDeniedException) {
            return handleAccessDeniedException((AccessDeniedException) exception);
        } else if (exception instanceof NoContentException) {
            return handleNoContentException((NoContentException) exception);
        } else if (exception instanceof IllegalArgumentException) {
            return handleIllegalArgumentException((IllegalArgumentException) exception);
        } else {
            return handleUnknownException(exception);
        }
    }

    @Nonnull
    private Response handleNoContentException(@Nonnull NoContentException exception) {
        String message = ExceptionUtils.getExceptionMessage(exception);
        LOG.warn("No content, message {}", message);
        return ResponseUtils.newResponse(Response.Status.NO_CONTENT, message);
    }

    @Nonnull
    private Response handleIllegalArgumentException(IllegalArgumentException exception) {
        String message = ExceptionUtils.getExceptionMessage(exception);
        LOG.warn("Illegal argument, message {}", message);
        return ResponseUtils.newResponse(Response.Status.BAD_REQUEST, message);
    }

    @Nonnull
    private Response handleUnknownException(@Nonnull Throwable exception) {
        String message = ExceptionUtils.getExceptionMessage(exception);
        if (exception.getClass().getName().equals("org.eclipse.jetty.io.EofException")) {
            // connection reset by client - not an error
            LOG.warn(message);
        } else {
            LOG.error(message, exception);
        }
        return ResponseUtils.newResponse(Response.Status.INTERNAL_SERVER_ERROR, message);
    }

    @Nonnull
    private Response handleCxfParamsException(@Nonnull WebApplicationException exception) {
        String message = exception.getCause().getMessage();
        LOG.warn(message);
        return ResponseUtils.newResponse(Response.Status.BAD_REQUEST, message);
    }

    @Nonnull
    private Response handleAccessDeniedException(@Nonnull AccessDeniedException exception) {
        String message = ExceptionUtils.getExceptionMessage(exception);
        LOG.warn("status code {}, message {}", Response.Status.FORBIDDEN.getStatusCode(), message);
        return ResponseUtils.newResponse(Response.Status.FORBIDDEN, message);
    }

    @Nonnull
    private Response handleWebApplicationException(@Nonnull WebApplicationException exception) {
        String message = getExceptionMessage(exception);
        String service = exception.getResponse().getHeaderString("X-qe-server-name");
        String host = exception.getResponse().getHeaderString("X-qe-hostname");
        String router = exception.getResponse().getHeaderString("X-qloud-router");
        String reason;
        Response effectiveResponse;
        if (service != null) {
            reason = String.format("Http status %s from %s at %s via %s %s", exception.getResponse().getStatus(), service, host, router, message);
            effectiveResponse = ResponseUtils.newResponse(Response.Status.INTERNAL_SERVER_ERROR, reason);
        } else if (router != null) {
            reason = String.format("Http status %s from %s %s", exception.getResponse().getStatus(), router, message);
            effectiveResponse = ResponseUtils.newResponse(Response.Status.INTERNAL_SERVER_ERROR, reason);
        } else {
            reason = message;
            effectiveResponse = exception.getResponse();
        }
        if (effectiveResponse.getStatus() >= 500) {
            LOG.error(reason + ", headers: " + effectiveResponse.getStringHeaders(), exception);
        } else {
            LOG.warn(reason, exception);
        }
        return effectiveResponse;
    }

    @Nonnull
    private String getExceptionMessage(@Nonnull WebApplicationException exception) {
        if (exception.getResponse().getEntity() == null) {
            return ExceptionUtils.getExceptionMessage(exception);
        }
        StringBuilder message = new StringBuilder();
        try {
            message.append(exception.getResponse().readEntity(String.class));
        } catch (IllegalStateException e) {
            // readEntity throws this if no content; assume message == null
            message.append(exception.getMessage());
        } catch (Exception e) {
            LOG.error("Error during reading message: {}", e.getMessage(), e);
            message.append(exception.getMessage());
        }
        message.append(" [");
        message.append(ExceptionUtils.getExceptionMessage(exception));
        message.append("]");
        return message.toString();
    }
}
