package engine

import (
	"html/template"
	"net"
	"net/http"
	"path"
	"strings"
	"sync"

	"go_dreamfactory/lego/sys/gin/render"
	"go_dreamfactory/lego/sys/log"
	"go_dreamfactory/lego/utils/codec"

	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
)

/*
	默认文件上传的最大尺寸
*/
const defaultMultipartMemory = 32 << 20 // 32 MB
/*
	默认可信代理
*/
var defaultTrustedCIDRs = []*net.IPNet{
	{ // 0.0.0.0/0 (IPv4)
		IP:   net.IP{0x0, 0x0, 0x0, 0x0},
		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
	},
	{ // ::/0 (IPv6)
		IP:   net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
	},
}

func NewEngine(log log.ILogger) (engine *Engine) {
	engine = &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		log:                    log,
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJSONPrefix:       "while(1);",
		trustedProxies:         []string{"0.0.0.0/0"},
		trustedCIDRs:           defaultTrustedCIDRs,
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return
}

var (
	default404Body = []byte("404 page not found")
	default405Body = []byte("405 method not allowed")
)
var mimePlain = []string{MIMEPlain}

type Engine struct {
	RouterGroup
	log        log.ILogger
	UseRawPath bool
	/*
		如果启用，路由器尝试修复当前请求路径，如果没有
		如果没有
		已为其注册句柄。
		第一个多余的路径元素，如 ../ 或 // 被删除。
		之后路由器对清理后的路径进行不区分大小写的查找。
		如果可以找到该路由的句柄，则路由器进行重定向
		到正确的路径，GET 请求的状态码为 301，而 GET 请求的状态码为 307
		所有其他请求方法。
		例如 /FOO 和 /..//Foo 可以重定向到 /foo。
		RedirectTrailingSlash 与此选项无关。
	*/
	RedirectFixedPath bool
	/*
		如果为真，路径值将不转义
	*/
	UnescapePathValues bool
	/*
		如果当前路由无法匹配，但启用自动重定向
		带有（不带）尾部斜杠的路径的处理程序存在。
		例如，如果 /foo/ 被请求，但路由只存在于 /foo，则
		对于 GET 请求，客户端被重定向到 /foo，http 状态码为 301
		对于所有其他请求方法，则为 307。
	*/
	RedirectTrailingSlash bool //

	/*
		如果启用，路由器检查是否允许其他方法
		当前路由，如果当前请求无法路由。
		如果是这种情况，则使用“不允许的方法”回答请求
		和 HTTP 状态码 405。
		如果不允许其他方法，则将请求委托给 NotFound
		处理程序。
	*/
	HandleMethodNotAllowed bool
	/*
		可以从 URL 中解析出一个参数，即使带有额外的斜杠。
	*/
	RemoveExtraSlash bool
	/*
		TrustedPlatform 如果设置为值 gin.Platform* 的常量，则信任由设置的标头
		那个平台，比如判断客户端IP
	*/
	TrustedPlatform string
	/*
		ForwardedByClientIP 如果启用，客户端 IP 将从请求的标头中解析
		匹配存储在 `(*gin.Engine).RemoteIPHeaders` 中的那些。 如果没有 IP
		fetched, 它回退到从获取的 IP
		`(*gin.Context).Request.RemoteAddr`。
		ForwardedByClientIP 布尔值
	*/
	ForwardedByClientIP bool
	/*
		RemoteIPHeaders 用于获取客户端 IP 时的 headers 列表
		`(*gin.Engine).ForwardedByClientIP` 为 `true` 并且
		`(*gin.Context).Request.RemoteAddr` 被至少一个匹配
		由 `(*gin.Engine).SetTrustedProxies()` 定义的列表的网络来源。
	*/
	RemoteIPHeaders []string
	/*
		文件上传的最大尺寸
	*/
	MaxMultipartMemory int64
	/*
		是否使用H2C
	*/
	UseH2C           bool
	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	noRoute          HandlersChain
	noMethod         HandlersChain
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	pool             sync.Pool
	trees            methodTrees
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
	trustedCIDRs     []*net.IPNet
}

func (this *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := this.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()
	this.handleHTTPRequest(c)
	this.pool.Put(c)
}

func (this *Engine) Handler() http.Handler {
	if !this.UseH2C {
		return this
	}

	h2s := &http2.Server{}
	return h2c.NewHandler(this, h2s)
}

/*
	使用中间件
*/
func (this *Engine) Use(middleware ...HandlerFunc) IRoutes {
	this.RouterGroup.Use(middleware...)
	this.rebuild404Handlers()
	this.rebuild405Handlers()
	return this
}

/*
	LoadHTMLGlob 加载由 glob 模式标识的 HTML 文件
	并将结果与 HTML 渲染器相关联。
*/
func (this *Engine) LoadHTMLGlob(pattern string) {
	left := this.delims.Left
	right := this.delims.Right
	templ := template.Must(template.New("").Delims(left, right).Funcs(this.FuncMap).ParseGlob(pattern))

	if this.log.Enabled(log.DebugLevel) {
		this.debugPrintLoadTemplate(templ)
		this.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: this.FuncMap, Delims: this.delims}
		return
	}

	this.SetHTMLTemplate(templ)
}

/*
LoadHTMLFiles 加载一段 HTML 文件
并将结果与 HTML 渲染器相关联。
*/
func (this *Engine) LoadHTMLFiles(files ...string) {
	if this.log.Enabled(log.DebugLevel) {
		this.HTMLRender = render.HTMLDebug{Files: files, FuncMap: this.FuncMap, Delims: this.delims}
		return
	}
	templ := template.Must(template.New("").Delims(this.delims.Left, this.delims.Right).Funcs(this.FuncMap).ParseFiles(files...))
	this.SetHTMLTemplate(templ)
}

func (this *Engine) SetHTMLTemplate(templ *template.Template) {
	if len(this.trees) > 0 {
		this.log.Warnf(`Since SetHTMLTemplate() is NOT thread-safe. It should only be called
			at initialization. ie. before any route is registered or the router is listening in a socket:
				router := gin.Default()
				router.SetHTMLTemplate(template) // << good place
			`)

	}
	this.HTMLRender = render.HTMLProduction{Template: templ.Funcs(this.FuncMap)}
}

/*
	设置template FuncMap
*/
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
	engine.FuncMap = funcMap
}

/*
	404 处理路由
*/
func (this *Engine) NoRoute(handlers ...HandlerFunc) {
	this.noRoute = handlers
	this.rebuild404Handlers()
}

/*
	没有找到对应的方法
*/
func (this *Engine) NoMethod(handlers ...HandlerFunc) {
	this.noMethod = handlers
	this.rebuild405Handlers()
}

func (engine *Engine) Routes() (routes RoutesInfo) {
	for _, tree := range engine.trees {
		routes = iterate("", tree.method, routes, tree.root)
	}
	return routes
}

/*
	设置信任代理
*/
func (this *Engine) SetTrustedProxies(trustedProxies []string) error {
	this.trustedProxies = trustedProxies
	return this.parseTrustedProxies()
}

func (this *Engine) addRoute(method, path string, handlers HandlersChain) {
	assert1(path[0] == '/', "path must begin with '/'")
	assert1(method != "", "HTTP method can not be empty")
	assert1(len(handlers) > 0, "there must be at least one handler")
	if this.log.Enabled(log.DebugLevel) {
		nuHandlers := len(handlers)
		handlerName := nameOfFunction(handlers.Last())
		this.log.Debugf("%s:%s --> %s handlers:%d", method, path, handlerName, nuHandlers)
	}
	root := this.trees.get(method)
	if root == nil {
		root = new(node)
		root.fullPath = "/"
		this.trees = append(this.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, handlers)
	// Update maxParams
	if paramsCount := countParams(path); paramsCount > this.maxParams {
		this.maxParams = paramsCount
	}

	if sectionsCount := countSections(path); sectionsCount > this.maxSections {
		this.maxSections = sectionsCount
	}
}

func (this *Engine) handleHTTPRequest(c *Context) {
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	unescape := false
	if this.UseRawPath && len(c.Request.URL.RawPath) > 0 {
		rPath = c.Request.URL.RawPath
		unescape = this.UnescapePathValues
	}
	if this.RemoveExtraSlash {
		rPath = cleanPath(rPath)
	}
	t := this.trees
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		if value.params != nil {
			c.Params = *value.params
		}
		if value.handlers != nil {
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		if httpMethod != http.MethodConnect && rPath != "/" {
			if value.tsr && this.RedirectTrailingSlash {
				this.redirectTrailingSlash(c)
				return
			}
			if this.RedirectFixedPath && this.redirectFixedPath(c, root, this.RedirectFixedPath) {
				return
			}
		}
		break
	}

	if this.HandleMethodNotAllowed {
		for _, tree := range this.trees {
			if tree.method == httpMethod {
				continue
			}
			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
				c.handlers = this.allNoMethod
				this.serveError(c, http.StatusMethodNotAllowed, default405Body)
				return
			}
		}
	}
	c.handlers = this.allNoRoute
	this.serveError(c, http.StatusNotFound, default404Body)
}

func (this *Engine) rebuild404Handlers() {
	this.allNoRoute = this.combineHandlers(this.noRoute)
}

func (this *Engine) rebuild405Handlers() {
	this.allNoMethod = this.combineHandlers(this.noMethod)
}

func (this *Engine) IsUnsafeTrustedProxies() bool {
	return this.isTrustedProxy(net.ParseIP("0.0.0.0")) || this.isTrustedProxy(net.ParseIP("::"))
}

//validateHeader 将解析 X-Forwarded-For 标头并返回受信任的客户端 IP 地址
func (this *Engine) validateHeader(header string) (clientIP string, valid bool) {
	if header == "" {
		return "", false
	}
	items := strings.Split(header, ",")
	for i := len(items) - 1; i >= 0; i-- {
		ipStr := strings.TrimSpace(items[i])
		ip := net.ParseIP(ipStr)
		if ip == nil {
			break
		}

		// X-Forwarded-For is appended by proxy
		// Check IPs in reverse order and stop when find untrusted proxy
		if (i == 0) || (!this.isTrustedProxy(ip)) {
			return ipStr, true
		}
	}
	return "", false
}

///目标Ip是否是可信
func (this *Engine) isTrustedProxy(ip net.IP) bool {
	if this.trustedCIDRs == nil {
		return false
	}
	for _, cidr := range this.trustedCIDRs {
		if cidr.Contains(ip) {
			return true
		}
	}
	return false
}

func (this *Engine) serveError(c *Context, code int, defaultMessage []byte) {
	c.writermem.status = code
	c.Next()
	if c.writermem.Written() {
		return
	}
	if c.writermem.Status() == code {
		c.writermem.Header()["Content-Type"] = mimePlain
		_, err := c.Writer.Write(defaultMessage)
		if err != nil {
			this.log.Errorf("[SYS-Gin] cannot write message to writer during serve error: %v", err)
		}
		return
	}
	c.writermem.WriteHeaderNow()
}

func (this *Engine) redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
	req := c.Request
	rPath := req.URL.Path

	if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
		req.URL.Path = codec.BytesToString(fixedPath)
		this.redirectRequest(c)
		return true
	}
	return false
}

func (this *Engine) redirectTrailingSlash(c *Context) {
	req := c.Request
	p := req.URL.Path
	if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
		p = prefix + "/" + req.URL.Path
	}
	req.URL.Path = p + "/"
	if length := len(p); length > 1 && p[length-1] == '/' {
		req.URL.Path = p[:length-1]
	}
	this.redirectRequest(c)
}

func (this *Engine) redirectRequest(c *Context) {
	req := c.Request
	rPath := req.URL.Path
	rURL := req.URL.String()

	code := http.StatusMovedPermanently // Permanent redirect, request with GET method
	if req.Method != http.MethodGet {
		code = http.StatusTemporaryRedirect
	}
	this.log.Debugf("redirecting request %d: %s --> %s", code, rPath, rURL)
	http.Redirect(c.Writer, req, rURL, code)
	c.writermem.WriteHeaderNow()
}

func (this *Engine) parseTrustedProxies() error {
	trustedCIDRs, err := this.prepareTrustedCIDRs()
	this.trustedCIDRs = trustedCIDRs
	return err
}

func (this *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
	if this.trustedProxies == nil {
		return nil, nil
	}

	cidr := make([]*net.IPNet, 0, len(this.trustedProxies))
	for _, trustedProxy := range this.trustedProxies {
		if !strings.Contains(trustedProxy, "/") {
			ip := parseIP(trustedProxy)
			if ip == nil {
				return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy}
			}

			switch len(ip) {
			case net.IPv4len:
				trustedProxy += "/32"
			case net.IPv6len:
				trustedProxy += "/128"
			}
		}
		_, cidrNet, err := net.ParseCIDR(trustedProxy)
		if err != nil {
			return cidr, err
		}
		cidr = append(cidr, cidrNet)
	}
	return cidr, nil
}

func (this *Engine) allocateContext() *Context {
	v := make(Params, 0, this.maxParams)
	skippedNodes := make([]skippedNode, 0, this.maxSections)
	return newContext(this.log, this, &v, &skippedNodes) //& Context{Log: this.log, engine: this, params: &v, skippedNodes: &skippedNodes}
}

//日志接口-------------------------------------------------------------
func (this *Engine) debugPrintLoadTemplate(tmpl *template.Template) {
	var buf strings.Builder
	for _, tmpl := range tmpl.Templates() {
		buf.WriteString("\t- ")
		buf.WriteString(tmpl.Name())
		buf.WriteString("\n")
	}
	format := "Loaded HTML Templates (%d): \n%s\n"
	if !strings.HasSuffix(format, "\n") {
		format += "\n"
	}
	this.log.Debugf(format, len(tmpl.Templates()), buf.String())

}
