package httpclient

import (
	"context"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"quenue/app/http/trace"
	"quenue/config"

	"github.com/SkyAPM/go2sky"
	"github.com/SkyAPM/go2sky/propagation"
	v3 "github.com/SkyAPM/go2sky/reporter/grpc/language-agent"
	"github.com/go-resty/resty/v2"
	"github.com/qit-team/snow-core/log/logger"
)

const (
	RetryCounts   = 2
	RetryInterval = 3 * time.Second
)

const componentIDGOHttpClient = 5005

type ClientConfig struct {
	ctx       context.Context
	client    *resty.Client
	tracer    *go2sky.Tracer
	extraTags map[string]string
}

type ClientOption func(*ClientConfig)

func WithClientTag(key string, value string) ClientOption {
	return func(c *ClientConfig) {
		if c.extraTags == nil {
			c.extraTags = make(map[string]string)
		}
		c.extraTags[key] = value
	}
}

func WithClient(client *resty.Client) ClientOption {
	return func(c *ClientConfig) {
		c.client = client
	}
}

func WithContext(ctx context.Context) ClientOption {
	return func(c *ClientConfig) {
		c.ctx = ctx
	}
}

type transport struct {
	*ClientConfig
	delegated http.RoundTripper
}

func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	span, err := t.tracer.CreateExitSpan(t.ctx, fmt.Sprintf("/%s%s", req.Method, req.URL.Path), req.Host, func(header string) error {
		// 将本层的调用链信息写入http头部, 传入到下一层调用, 当前使用v3版本的协议
		// https://github.com/apache/skywalking/blob/master/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md
		req.Header.Set(propagation.Header, header)
		return nil
	})
	if err != nil {
		return t.delegated.RoundTrip(req)
	}
	defer span.End()
	span.SetComponent(componentIDGOHttpClient)
	for k, v := range t.extraTags {
		span.Tag(go2sky.Tag(k), v)
	}
	span.Tag(go2sky.TagHTTPMethod, req.Method)
	span.Tag(go2sky.TagURL, req.URL.String())
	span.SetSpanLayer(v3.SpanLayer_Http)
	resp, err = t.delegated.RoundTrip(req)
	if err != nil {
		span.Error(time.Now(), err.Error())
		return
	}
	span.Tag(go2sky.TagStatusCode, strconv.Itoa(resp.StatusCode))
	if resp.StatusCode >= http.StatusBadRequest {
		span.Error(time.Now(), "Errors on handling client")
	}
	return resp, nil
}

func NewClient(ctx context.Context, options ...ClientOption) (client *resty.Client) {
	client = resty.New()
	if config.IsDebug() {
		client.SetDebug(true).EnableTrace()
	}

	var (
		tracer *go2sky.Tracer
		err    error
	)
	if len(config.GetConf().SkyWalkingOapServer) > 0 && config.IsEnvEqual(config.ProdEnv) {
		tracer, err = trace.Tracer()
		if err != nil {
			logger.Error(ctx, "NewClient:Tracer", err.Error())
		}
	}
	if tracer != nil {
		co := &ClientConfig{ctx: ctx, tracer: tracer}
		for _, option := range options {
			option(co)
		}
		if co.client == nil {
			co.client = client
		}
		tp := &transport{
			ClientConfig: co,
			delegated:    http.DefaultTransport,
		}
		if co.client.GetClient().Transport != nil {
			tp.delegated = co.client.GetClient().Transport
		}
		co.client.SetTransport(tp)
	}

	client.OnBeforeRequest(func(ct *resty.Client, req *resty.Request) error {
		//req.SetContext(c)
		logger.Info(ctx, "OnBeforeRequest", logger.NewWithField("url", req.URL))
		return nil // if its success otherwise return error
	})
	// Registering Response Middleware
	client.OnAfterResponse(func(ct *resty.Client, resp *resty.Response) error {
		logger.Info(ctx, "OnAfterResponse", logger.NewWithField("url", resp.Request.URL), logger.NewWithField("request", resp.Request.RawRequest), logger.NewWithField("response", resp.String()))
		return nil
	})
	return client
}

func NewClientWithRetry(ctx context.Context, retryCounts int, retryInterval time.Duration, options ...ClientOption) (client *resty.Client) {
	client = resty.New()
	if config.IsDebug() {
		client.SetDebug(true).EnableTrace()
	}
	if retryCounts == 0 {
		retryCounts = RetryCounts
	}
	if retryInterval.Seconds() == 0.0 {
		retryInterval = RetryInterval
	}
	client.SetRetryCount(retryCounts).SetRetryMaxWaitTime(retryInterval)

	var (
		tracer *go2sky.Tracer
		err    error
	)
	if len(config.GetConf().SkyWalkingOapServer) > 0 && config.IsEnvEqual(config.ProdEnv) {
		tracer, err = trace.Tracer()
		if err != nil {
			logger.Error(ctx, "NewClient:Tracer", err.Error())
		}
	}
	if tracer != nil {
		co := &ClientConfig{ctx: ctx, tracer: tracer}
		for _, option := range options {
			option(co)
		}
		if co.client == nil {
			co.client = client
		}
		tp := &transport{
			ClientConfig: co,
			delegated:    http.DefaultTransport,
		}
		if co.client.GetClient().Transport != nil {
			tp.delegated = co.client.GetClient().Transport
		}
		co.client.SetTransport(tp)
	}

	client.OnBeforeRequest(func(ct *resty.Client, req *resty.Request) error {
		logger.Info(ctx, "OnBeforeRequest", logger.NewWithField("url", req.URL))
		return nil // if its success otherwise return error
	})
	// Registering Response Middleware
	client.OnAfterResponse(func(ct *resty.Client, resp *resty.Response) error {
		logger.Info(ctx, "OnAfterResponse", logger.NewWithField("url", resp.Request.URL), logger.NewWithField("request", resp.Request.RawRequest), logger.NewWithField("response", resp.String()))
		return nil
	})
	return client
}