/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.skywalking.apm.plugin.asynchttpclient.v2;

import io.netty.channel.Channel;
import io.netty.handler.codec.http.HttpHeaders;
import java.net.InetSocketAddress;
import java.util.List;
import javax.net.ssl.SSLSession;
import org.apache.skywalking.apm.agent.core.context.tag.Tags;
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
import org.apache.skywalking.apm.agent.core.logging.api.ILog;
import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
import org.asynchttpclient.AsyncCompletionHandlerBase;
import org.asynchttpclient.AsyncHandler;
import org.asynchttpclient.HttpResponseBodyPart;
import org.asynchttpclient.HttpResponseStatus;
import org.asynchttpclient.netty.request.NettyRequest;

/**
 * {@link AsyncHandlerWrapper} wrapper the {@link AsyncHandler} object for tracing.
 * if userAsyncHandler is null, we will set {@link AsyncCompletionHandlerBase} to avoid NPE.
 */
public class AsyncHandlerWrapper implements AsyncHandler {

    private final AsyncHandler userAsyncHandler;
    private final AbstractSpan asyncSpan;

    private static final ILog LOGGER = LogManager.getLogger(AsyncHandlerWrapper.class);

    public AsyncHandlerWrapper(AsyncHandler asyncHandler, AbstractSpan span) {
        this.userAsyncHandler = asyncHandler == null ? new AsyncCompletionHandlerBase() : asyncHandler;
        this.asyncSpan = span;
    }

    @Override
    public State onStatusReceived(final HttpResponseStatus httpResponseStatus) throws Exception {
        int statusCode = httpResponseStatus.getStatusCode();
        Tags.STATUS_CODE.set(asyncSpan, String.valueOf(statusCode));
        if (statusCode >= 400) {
            asyncSpan.errorOccurred();
        }
        return userAsyncHandler.onStatusReceived(httpResponseStatus);
    }

    @Override
    public State onHeadersReceived(final HttpHeaders httpHeaders) throws Exception {
        return userAsyncHandler.onHeadersReceived(httpHeaders);
    }

    @Override
    public State onBodyPartReceived(final HttpResponseBodyPart httpResponseBodyPart) throws Exception {
        return userAsyncHandler.onBodyPartReceived(httpResponseBodyPart);
    }

    @Override
    public State onTrailingHeadersReceived(final HttpHeaders headers) throws Exception {
        return userAsyncHandler.onTrailingHeadersReceived(headers);
    }

    @Override
    public void onThrowable(final Throwable throwable) {
        try {
            asyncSpan.log(throwable);
            asyncSpan.asyncFinish();
        } catch (Exception e) {
            LOGGER.error("Failed to notify the async span stop.", e);
        }
        userAsyncHandler.onThrowable(throwable);
    }

    @Override
    public Object onCompleted() throws Exception {
        try {
            asyncSpan.asyncFinish();
        } catch (Exception e) {
            LOGGER.error("Failed to notify the async span stop.", e);
        }
        return userAsyncHandler.onCompleted();
    }

    @Override
    public void onHostnameResolutionAttempt(final String name) {
        userAsyncHandler.onHostnameResolutionAttempt(name);
    }

    @Override
    public void onHostnameResolutionFailure(final String name, final Throwable cause) {
        userAsyncHandler.onHostnameResolutionFailure(name, cause);
    }

    @Override
    public void onTcpConnectAttempt(final InetSocketAddress remoteAddress) {
        userAsyncHandler.onTcpConnectAttempt(remoteAddress);
    }

    @Override
    public void onTcpConnectSuccess(final InetSocketAddress remoteAddress, final Channel connection) {
        userAsyncHandler.onTcpConnectSuccess(remoteAddress, connection);
    }

    @Override
    public void onTcpConnectFailure(final InetSocketAddress remoteAddress, final Throwable cause) {
        userAsyncHandler.onTcpConnectFailure(remoteAddress, cause);
    }

    @Override
    public void onTlsHandshakeAttempt() {
        userAsyncHandler.onTlsHandshakeAttempt();
    }

    @Override
    public void onTlsHandshakeSuccess(final SSLSession sslSession) {
        userAsyncHandler.onTlsHandshakeSuccess(sslSession);
    }

    @Override
    public void onTlsHandshakeFailure(final Throwable cause) {
        userAsyncHandler.onTlsHandshakeFailure(cause);
    }

    @Override
    public void onConnectionPoolAttempt() {
        userAsyncHandler.onConnectionPoolAttempt();
    }

    @Override
    public void onConnectionPooled(final Channel connection) {
        userAsyncHandler.onConnectionPooled(connection);
    }

    @Override
    public void onConnectionOffer(final Channel connection) {
        userAsyncHandler.onConnectionOffer(connection);
    }

    @Override
    public void onRequestSend(final NettyRequest request) {
        userAsyncHandler.onRequestSend(request);
    }

    @Override
    public void onRetry() {
        userAsyncHandler.onRetry();
    }

    @Override
    public void onHostnameResolutionSuccess(final String name, final List list) {
        userAsyncHandler.onHostnameResolutionSuccess(name, list);
    }
}
