package com.github.hippo.zipkin;

import brave.Span;
import brave.Tracing;
import brave.propagation.B3Propagation;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.propagation.TraceContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import zipkin2.codec.SpanBytesEncoder;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.urlconnection.URLConnectionSender;

import java.util.concurrent.TimeUnit;

/**
 * zipkin 实现类
 */
@Component
public class ZipkinRecordImpl implements ZipkinRecordService {


    private static final Logger LOGGER = LoggerFactory.getLogger(ZipkinRecordImpl.class);

    @Value("${hippo.zipkin.url:}")
    private String zipkinUrl;

    private static boolean isInitError;


    @Override
    public ZipkinResp start(ZipkinReq zipkinReq) {

        if (isInitError || zipkinReq == null || StringUtils.isBlank(zipkinUrl) || StringUtils.isBlank(zipkinReq.getServiceName())) {
            return null;
        }
        Tracing tracing = null;
        try {
            Span span;
            tracing = Tracing.newBuilder()
                    .localServiceName(zipkinReq.getServiceName())
                    .spanReporter(AsyncReporter.builder(URLConnectionSender.create(zipkinUrl))
                            .closeTimeout(100, TimeUnit.MILLISECONDS)
                            .build(SpanBytesEncoder.JSON_V2))
                    .propagationFactory(B3Propagation.FACTORY)
                    .currentTraceContext(ThreadLocalCurrentTraceContext.create())
                    .build();
            if (zipkinReq.getParentSpanId() != null && zipkinReq.getParentTraceId() != null) {
                span = tracing.tracer().newChild(TraceContext.newBuilder()
                        .parentId(zipkinReq.getParentSpanId())
                        .traceId(zipkinReq.getParentTraceId())
                        .spanId(zipkinReq.getParentSpanId()).build());
            } else {
                span = tracing.tracer().newTrace();
            }

            if (StringUtils.isNotBlank(zipkinReq.getAnnotate())) {
                span.annotate(zipkinReq.getAnnotate());
            }
            if (StringUtils.isNotBlank(zipkinReq.getMethodName())) {
                span.name(zipkinReq.getMethodName());
            }

            if (zipkinReq.getSpanKind() != null) {
                if (zipkinReq.getSpanKind() == SpanKind.CLIENT) {
                    span.kind(Span.Kind.CLIENT);
                } else {
                    span.kind(Span.Kind.SERVER);
                }
            }
            if (zipkinReq.getTags() != null && !zipkinReq.getTags().isEmpty()) {
                for (String key : zipkinReq.getTags().keySet()) {
                    span.tag(key, zipkinReq.getTags().get(key));
                }
            }
            span.start();
            ZipkinResp resp = new ZipkinResp();
            resp.setParentSpanId(Long.toHexString(span.context().spanId()));
            resp.setParentTraceId(Long.toHexString(span.context().traceId()));
            resp.setSpan(span);
            resp.setTracing(tracing);
            return resp;
        } catch (Exception e) {
            LOGGER.error("zipkin start error:" + zipkinUrl, e);
            close(tracing);
            isInitError = true;
        }
        return null;
    }

    private void close(Tracing tracing) {
        if (tracing != null) {
            tracing.close();
        }
    }

    @Override
    public void finish(ZipkinResp zipkinResp) {
        try {
            if (zipkinResp != null && zipkinResp.getSpan() != null) {
                ((Span) zipkinResp.getSpan()).finish();
            }
        } catch (Exception e) {
            LOGGER.error("zipkin finish error:" + zipkinUrl, e);
        } finally {
            close((Tracing) zipkinResp.getTracing());
        }
    }

    @Override
    public void error(ZipkinResp zipkinResp, Throwable throwable) {
        try {
            if (zipkinResp != null && zipkinResp.getSpan() != null) {
                ((Span) zipkinResp.getSpan()).error(throwable);
            }
        } catch (Exception e) {
            LOGGER.error("zipkin error error:" + zipkinUrl, e);
            close((Tracing) zipkinResp.getTracing());
        }
    }


    @Override
    public void finish(ZipkinResp zipkinResp, Throwable throwable) {
        try {
            if (zipkinResp != null && zipkinResp.getSpan() != null) {
                ((Span) zipkinResp.getSpan()).error(throwable);
                ((Span) zipkinResp.getSpan()).finish();
            }
        } catch (Exception e) {
            LOGGER.error("zipkin finish[throwable] error:" + zipkinUrl, e);
        } finally {
            close((Tracing) zipkinResp.getTracing());
        }
    }
}