package handler

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"strconv"
	"strings"

	"git.pyer.club/kingecg/gohttpd/model"
	"git.pyer.club/kingecg/gologger"
)

type ProxyHandler struct {
	proxy     []*httputil.ReverseProxy
	Upstreams []string
	count     int
}

func (p *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	l := gologger.GetLogger("Proxy")
	originalUrl := r.Host + r.URL.String()
	s, err := r.Cookie("s")
	var proxyIndex int
	if err != nil {
		proxyIndex = p.count
		p.count++
		if p.count >= len(p.proxy) {
			p.count = 0
		}
	} else {
		proxyIndex, _ = strconv.Atoi(s.Value)
	}

	l.Info(fmt.Sprintf("proxy %s to %s", originalUrl, p.Upstreams[proxyIndex]))
	p.proxy[proxyIndex].ServeHTTP(w, r)
}

/*
*
init httputil.ReverseProxy instance and add path rewrite and add session-sticky cookie to response
@param upstream upstream server url
@param path http path config
@param index the proxy index in upstreams

@return httputil.ReverseProxy instance

what is the directive? its stands for update of request, like HostSchemas, Path, RemoveCookie, etc.
eg: HostSchemas $target
it stande for replace req url host and schema according to $target url. $target == upstream
*/
func makeProxy(upstream string, path *model.HttpPath, index int) *httputil.ReverseProxy {
	p := &httputil.ReverseProxy{}
	directiveHandlers := []func(r *http.Request){}
	if len(path.Directives) > 0 {
		for _, directive := range path.Directives {
			d := strings.Replace(string(directive), "$target", upstream, 1)
			dh := GetUpdaterFn(d)
			if dh != nil {
				directiveHandlers = append(directiveHandlers, dh)
			}
		}
	}
	p.Director = func(req *http.Request) {
		for _, handler := range directiveHandlers {
			handler(req)
		}
	}

	p.ModifyResponse = func(resp *http.Response) error {
		hasSticky := false
		for _, cookie := range resp.Cookies() {
			if cookie.Name == "s" {
				hasSticky = true
				break
			}
		}
		if !hasSticky {
			c := http.Cookie{
				Name:  "s",
				Value: strconv.Itoa(index),
			}
			resp.Header.Add("Set-Cookie", c.String())
		}
		return nil
	}
	return p
}

func NewProxyHandler(p *model.HttpPath) *ProxyHandler {
	upstreamCount := len(p.Upstreams)
	if upstreamCount == 0 {
		panic("no upstream defined")
	}
	ph := &ProxyHandler{
		Upstreams: p.Upstreams,
	}
	ph.proxy = make([]*httputil.ReverseProxy, upstreamCount)

	for index, upstream := range p.Upstreams {
		ph.proxy[index] = makeProxy(upstream, p, index)
	}
	return ph
}
