package utils

import (
	"compress/flate"
	"compress/gzip"
	"compress/zlib"
	"context"
	"crypto/tls"
	"fmt"
	"golang.org/x/net/proxy"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Error Code接口
type IErrorCode interface {
	// 转换为int
	ToInt() int
	// 方便与int比较
	Equals(errCode int) bool
	// 获取错误信息
	ErrMsg(args ...interface{}) string
}

//easyjson:json
type Res struct {
	Head ResHead     `json:"head"`
	Data interface{} `json:"data,omitempty"`
}

//easyjson:json
type ResHead struct {
	ErrCode    int    `json:"errcode"`
	ErrMsg     string `json:"errmsg,omitempty"`
	Detail     string `json:"-"`
	HttpStatus int    `json:"-"`
}

func E2(errDetail interface{}, errCode IErrorCode, args ...interface{}) *Res {
	errMsg := errCode.ErrMsg(args...)
	return E(errCode.ToInt(), errMsg, errDetail)
}

func E(status int, errMsg string, errDetail interface{}) *Res {
	var msg, detail string
	if nil != errDetail {
		detail = strings.TrimSpace(fmt.Sprintf("%s", errDetail))
	}
	msg = strings.TrimSpace(errMsg)
	return &Res{
		Head: ResHead{
			ErrCode:    status,
			ErrMsg:     msg,
			Detail:     detail,
			HttpStatus: http.StatusInternalServerError,
		},
	}
}

func R(data interface{}) *Res {
	return &Res{
		Head: ResHead{
			ErrCode:    0,
			ErrMsg:     "",
			HttpStatus: http.StatusOK,
		},
		Data: data,
	}
}

// http请求相关默认配置
const (
	DefaultHttpDialTimeout         = 20 * time.Second
	DefaultHttpKeepAlive           = 120 * time.Second
	DefaultHttpMaxIdleConns        = 1000
	DefaultHttpMaxIdleConnsPerHost = 1000
	DefaultHttpIdleConnTimeout     = 90 * time.Second
	DefaultHttpTimeout             = 30 * time.Second
)

// http请求配置
type RequestPromise struct {
	headers     http.Header
	encoding    Charset
	timeout     time.Duration
	proxy       func(*http.Request) (*url.URL, error)
	dialContext func(ctx context.Context, network, addr string) (net.Conn, error)
	client      *http.Client
	isSkipTls   bool
}

// 返回一个http请求配置对象,默认带上压缩头
func NewRequest() *RequestPromise {
	return (&RequestPromise{}).
		SetHeader("Accept-Encoding", "gzip, deflate, zlib")
}

// 返回一个http请求配置对象,默认带上压缩头和Content-Type = application/json; charset=utf-8
func JSONRequest() *RequestPromise {
	return (&RequestPromise{}).
		SetHeader("Accept-Encoding", "gzip, deflate, zlib").
		SetHeader("Content-Type", "application/json; charset=utf-8")
}

// 返回一个http请求配置对象,默认带上压缩头和Content-Type = application/xml; charset=utf-8
func XMLRequest() *RequestPromise {
	return (&RequestPromise{}).
		SetHeader("Accept-Encoding", "gzip, deflate, zlib").
		SetHeader("Content-Type", "application/xml; charset=utf-8")
}

// 返回一个采用了连接池和Keepalive配置的http client,可以配合RequestPromise的SetClient函数使用
// 默认不使用它,而是每次请求新建连接
func NewPoolingHttpClient() *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			DialContext: (&net.Dialer{
				Timeout:   DefaultHttpDialTimeout,
				KeepAlive: DefaultHttpKeepAlive,
			}).DialContext,
			MaxIdleConns:        DefaultHttpMaxIdleConns,
			MaxIdleConnsPerHost: DefaultHttpMaxIdleConnsPerHost,
			IdleConnTimeout:     DefaultHttpIdleConnTimeout,
		},
		Timeout: DefaultHttpTimeout, // 此处设置小于等于零的值,意为不超时
	}
}

// 设置https忽略本地证书校验
func (v *RequestPromise) SetSkipTls() *RequestPromise {
	v.isSkipTls = true
	return v
}

// 设置http header
func (v *RequestPromise) SetHeader(key string, value string) *RequestPromise {
	if len(strings.TrimSpace(key)) == 0 {
		return v
	}
	key = strings.TrimSpace(key)
	if nil == v.headers {
		v.headers = make(http.Header)
	}
	v.headers.Set(key, value)
	return v
}

// 设置http响应的编码,默认utf8
func (v *RequestPromise) SetEncoding(encoding Charset) *RequestPromise {
	if encoding == UTF8 {
		return v
	}
	v.encoding = encoding
	return v
}

// 设置超时时间,从连接到接收到响应的总时间
// 如果此处不设置则采用http client中设置的超时时间,默认http client超时时间30秒
// 如果此处设置不等于零的值,则覆盖http client中设置的超时时间
// 如果此处设置小于零的值,意为不超时
func (v *RequestPromise) SetTimeout(timeout time.Duration) *RequestPromise {
	if timeout == 0 {
		return v
	}
	v.timeout = timeout
	return v
}

// 设置http或https代理,默认无代理
func (v *RequestPromise) SetHttpProxy(proxyUri string) *RequestPromise {
	if len(strings.TrimSpace(proxyUri)) == 0 {
		return v
	}
	proxyUri = strings.TrimSpace(proxyUri)
	uri, err := (&url.URL{}).Parse(proxyUri)
	if nil != err {
		return v
	}
	v.proxy = http.ProxyURL(uri)
	return v
}

// 设置socket5代理,默认无代理
func (v *RequestPromise) SetSocket5Proxy(proxyUri string) *RequestPromise {
	if len(strings.TrimSpace(proxyUri)) == 0 {
		return v
	}
	proxyUri = strings.TrimSpace(proxyUri)
	dialer, err := proxy.SOCKS5("tcp", proxyUri, nil, proxy.Direct)
	if nil != err {
		return v
	}
	v.dialContext = func(_ context.Context, network string, address string) (net.Conn, error) {
		return dialer.Dial(network, address)
	}
	return v
}

// 设置事先实例化好的http client,默认每次请求会新建一个http client
func (v *RequestPromise) SetClient(client *http.Client) *RequestPromise {
	if nil == client {
		return v
	}
	v.client = client
	return v
}

// 发起请求并返回响应内容
// FORM方式提交数据请设置Content-Type=application/x-www-form-urlencoded请求头,且io.Reader传url.Values.Encode得到的字符串的reader
func (v *RequestPromise) Call(httpMethod string, targetUri string, data io.Reader) ([]byte, error) {
	targetUri = strings.TrimSpace(targetUri)
	if len(targetUri) == 0 {
		return nil, nil
	}

	// http request handle
	if len(strings.TrimSpace(httpMethod)) == 0 {
		httpMethod = http.MethodGet
	} else {
		httpMethod = strings.ToUpper(strings.TrimSpace(httpMethod))
	}
	req, err := http.NewRequest(httpMethod, targetUri, data)
	if err != nil {
		return nil, err
	}
	if nil != v.headers {
		req.Header = v.headers
	}

	v.initClient()

	// send http request & get http response
	resp, err := v.client.Do(req)
	if err != nil {
		return nil, err
	}

	return v.readResponseBody(resp)
}

func (v *RequestPromise) initClient() {
	// http client handle
	if nil == v.client { // create new http client instance
		v.client = &http.Client{Timeout: DefaultHttpTimeout} // default timeout
	}
	if v.timeout < 0 {
		v.timeout = DefaultHttpTimeout // default timeout
	}
	if v.timeout > 0 {
		v.client.Timeout = v.timeout
	}
	if v.isSkipTls {
		if nil == v.client.Transport {
			v.client.Transport = &http.Transport{}
		}
		transport := (v.client.Transport).(*http.Transport)
		transport.TLSClientConfig = &tls.Config{
			InsecureSkipVerify: true,
			//VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
			//	return nil
			//},
		}
	}
	if nil != v.proxy || nil != v.dialContext {
		if nil == v.client.Transport {
			v.client.Transport = &http.Transport{}
		}
		transport := (v.client.Transport).(*http.Transport)
		if nil != v.proxy {
			transport.Proxy = v.proxy
		}
		if nil != v.dialContext {
			transport.DialContext = v.dialContext
		}
	}
}

func (v *RequestPromise) readResponseBody(resp *http.Response) ([]byte, error) {
	defer func(body io.ReadCloser) {
		_ = body.Close()
	}(resp.Body)

	var reader io.ReadCloser
	switch resp.Header.Get("Content-Encoding") {
	case "gzip":
		reader, _ = gzip.NewReader(resp.Body)
		defer func(reader io.Closer) {
			_ = reader.Close()
		}(reader)
	case "deflate":
		reader = flate.NewReader(resp.Body)
		defer func(reader io.Closer) {
			_ = reader.Close()
		}(reader)
	case "zlib":
		reader, _ = zlib.NewReader(resp.Body)
		defer func(reader io.Closer) {
			_ = reader.Close()
		}(reader)
	default:
		reader = resp.Body
	}

	body, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	if v.encoding != "" {
		body = ConvertToEncodingBytes(body, v.encoding)
	}
	return body, nil
}

// 获取客户端IP地址
func GetRemoteIP(req *http.Request) string {
	remoteAddr := req.RemoteAddr
	if ip := req.Header.Get("Remote_addr"); ip != "" {
		remoteAddr = ip
	} else {
		remoteAddr, _, _ = net.SplitHostPort(remoteAddr)
	}

	if remoteAddr == "::1" {
		remoteAddr = "127.0.0.1"
	}
	return remoteAddr
}