package proxy

import (
	"errors"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"time"
)

func customizedTransport() http.RoundTripper {
	defaultTransPtr, ok := http.DefaultTransport.(*http.Transport)
	if !ok {
		return http.DefaultTransport
	}
	return &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   3 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		MaxIdleConns: 100,
		// increase conn count for better concurrency
		MaxIdleConnsPerHost:   10,
		ResponseHeaderTimeout: 30 * time.Second,
		IdleConnTimeout:       defaultTransPtr.IdleConnTimeout,
		TLSHandshakeTimeout:   defaultTransPtr.TLSHandshakeTimeout,
		ExpectContinueTimeout: defaultTransPtr.ExpectContinueTimeout,
	}
}

// reverseProxyWrapper wrap native ReversePorxy for customization
type ReverseProxyWrapper struct {
	target *url.URL
	*httputil.ReverseProxy
}

func (wrapper *ReverseProxyWrapper) WithPathPerfixRewrite(oldStr string, newStr string) *ReverseProxyWrapper {
	origin := wrapper.ReverseProxy
	p := &httputil.ReverseProxy{
		Transport: origin.Transport,
		Director: func(req *http.Request) {
			req.URL.Path = strings.Replace(req.URL.Path, oldStr, newStr, 1)
			req.URL.RawPath = strings.Replace(req.URL.RawPath, oldStr, newStr, 1)
			origin.Director(req)
		},
		ModifyResponse: origin.ModifyResponse,
	}
	return &ReverseProxyWrapper{target: wrapper.target, ReverseProxy: p}
}

func (wrapper *ReverseProxyWrapper) WithActionFormat() *ReverseProxyWrapper {
	origin := wrapper.ReverseProxy
	p := &httputil.ReverseProxy{
		Transport: origin.Transport,
		Director: func(req *http.Request) {
			epath := req.URL.EscapedPath()
			index := strings.LastIndex(epath, "/")
			if index != 1 && index != len(epath)-1 {
				action := epath[index+1:]
				formated := strings.Replace(epath, "/"+action, ":"+action, 1)
				req.URL.Path = formated
				req.URL.RawPath = formated
			}
			origin.Director(req)
		},
		ModifyResponse: origin.ModifyResponse,
	}
	return &ReverseProxyWrapper{target: wrapper.target, ReverseProxy: p}
}

// endpoint must has sheme
func NewReverseProxy(endpoint string) (*ReverseProxyWrapper, error) {
	u, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}
	if u.Scheme == "" {
		return nil, errors.New("scheme is empty")
	}
	if u.Host == "" {
		return nil, errors.New("host is empty")
	}
	rp := &httputil.ReverseProxy{
		Transport: customizedTransport(),
		Director: func(req *http.Request) {
			req.URL.Scheme = u.Scheme
			req.URL.Host = u.Host
			req.Host = u.Host
		},
		ModifyResponse: func(response *http.Response) error {
			return nil
		},
	}
	return &ReverseProxyWrapper{
		target:       u,
		ReverseProxy: rp,
	}, nil
}
