package controller

import (
	"fmt"
	plugin "github.com/fatedier/frp/pkg/plugin/server"
	"github.com/gin-gonic/gin"
	"gopkg.in/ini.v1"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type HandleController struct {
	CommonInfo CommonInfo
	Tokens     map[string]TokenInfo
	Ports      map[string][]string
	Domains    map[string][]string
	Subdomains map[string][]string
	ConfigFile string
	IniFile    *ini.File
	Version    string
}

func NewHandleController(config *HandleController) *HandleController {
	return config
}

func (c *HandleController) Register(rootDir string, engine *gin.Engine) {
	assets := filepath.Join(rootDir, "assets")
	_, err := os.Stat(assets)
	if err != nil && !os.IsExist(err) {
		assets = "./assets"
	}

	engine.Delims("${", "}")
	engine.LoadHTMLGlob(filepath.Join(assets, "templates/*"))
	engine.POST("/handler", c.MakeHandlerFunc())

	var group *gin.RouterGroup
	if len(c.CommonInfo.User) != 0 {
		group = engine.Group("/", gin.BasicAuthForRealm(gin.Accounts{
			c.CommonInfo.User: c.CommonInfo.Pwd,
		}, "Restricted"))
	} else {
		group = engine.Group("/")
	}
	group.Static("/static", filepath.Join(assets, "static"))
	group.GET("/", c.MakeManagerFunc())
	group.GET("/lang.json", c.MakeLangFunc())
	group.GET("/tokens", c.MakeQueryTokensFunc())
	group.POST("/add", c.MakeAddTokenFunc())
	group.POST("/update", c.MakeUpdateTokensFunc())
	group.POST("/remove", c.MakeRemoveTokensFunc())
	group.POST("/disable", c.MakeDisableTokensFunc())
	group.POST("/enable", c.MakeEnableTokensFunc())
	group.GET("/proxy/*serverApi", c.MakeProxyFunc())
}

func (c *HandleController) HandleLogin(content *plugin.LoginContent) plugin.Response {
	token := content.Metas["token"]
	user := content.User
	return c.JudgeToken(user, token)
}

func (c *HandleController) HandleNewProxy(content *plugin.NewProxyContent) plugin.Response {
	token := content.User.Metas["token"]
	user := content.User.User
	judgeToken := c.JudgeToken(user, token)
	if judgeToken.Reject {
		return judgeToken
	}
	return c.JudgePort(content)
}

func (c *HandleController) HandlePing(content *plugin.PingContent) plugin.Response {
	token := content.User.Metas["token"]
	user := content.User.User
	return c.JudgeToken(user, token)
}

func (c *HandleController) HandleNewWorkConn(content *plugin.NewWorkConnContent) plugin.Response {
	token := content.User.Metas["token"]
	user := content.User.User
	return c.JudgeToken(user, token)
}

func (c *HandleController) HandleNewUserConn(content *plugin.NewUserConnContent) plugin.Response {
	token := content.User.Metas["token"]
	user := content.User.User
	return c.JudgeToken(user, token)
}

func (c *HandleController) JudgeToken(user string, token string) plugin.Response {
	var res plugin.Response
	if len(c.Tokens) == 0 {
		res.Unchange = true
	} else if user == "" || token == "" {
		res.Reject = true
		res.RejectReason = "user or meta token can not be empty"
	} else if info, exist := c.Tokens[user]; exist {
		if !info.Status {
			res.Reject = true
			res.RejectReason = fmt.Sprintf("user [%s] is disabled", user)
		} else {
			if info.Token != token {
				res.Reject = true
				res.RejectReason = fmt.Sprintf("invalid meta token for user [%s]", user)
			} else {
				res.Unchange = true
			}
		}
	} else {
		res.Reject = true
		res.RejectReason = fmt.Sprintf("user [%s] not exist", user)
	}

	return res
}

func (c *HandleController) JudgePort(content *plugin.NewProxyContent) plugin.Response {
	var res plugin.Response
	var portErr error
	var reject = false
	supportProxyTypes := []string{
		"tcp", "tcpmux", "udp", "http", "https",
	}
	proxyType := content.ProxyType

	if StringIndexOf(proxyType, supportProxyTypes) == -1 {
		log.Printf("proxy type [%v] not support, plugin do nothing", proxyType)
		res.Unchange = true
		return res
	}

	user := content.User.User
	userPort := content.RemotePort
	userDomains := content.CustomDomains
	userSubdomain := content.SubDomain

	portAllowed := true
	if proxyType == "tcp" || proxyType == "udp" {
		portAllowed = false
		if _, exist := c.Ports[user]; exist {
			for _, port := range c.Ports[user] {
				if strings.Contains(port, "-") {
					allowedRanges := strings.Split(port, "-")
					if len(allowedRanges) != 2 {
						portErr = fmt.Errorf("user [%v] port range [%v] format error", user, port)
						break
					}
					start, err := strconv.Atoi(strings.TrimSpace(allowedRanges[0]))
					if err != nil {
						portErr = fmt.Errorf("user [%v] port rang [%v] start port [%v] is not a number", user, port, allowedRanges[0])
						break
					}
					end, err := strconv.Atoi(strings.TrimSpace(allowedRanges[1]))
					if err != nil {
						portErr = fmt.Errorf("user [%v] port rang [%v] end port [%v] is not a number", user, port, allowedRanges[0])
						break
					}
					if max(userPort, start) == userPort && min(userPort, end) == userPort {
						portAllowed = true
						break
					}
				} else {
					allowed, err := strconv.Atoi(port)
					if err != nil {
						portErr = fmt.Errorf("user [%v] allowed port [%v] is not a number", user, port)
					}
					if allowed == userPort {
						portAllowed = true
						break
					}
				}
			}
		} else {
			portAllowed = true
		}
	}
	if !portAllowed {
		if portErr == nil {
			portErr = fmt.Errorf("user [%v] port [%v] is not allowed", user, userPort)
		}
		reject = true
	}

	domainAllowed := true
	if proxyType == "http" || proxyType == "https" || proxyType == "tcpmux" {
		if portAllowed {
			if _, exist := c.Domains[user]; exist {
				for _, userDomain := range userDomains {
					if StringIndexOf(userDomain, c.Domains[user]) == -1 {
						domainAllowed = false
						break
					}
				}
			}
			if !domainAllowed {
				portErr = fmt.Errorf("user [%v] domain [%v] is not allowed", user, strings.Join(userDomains, ","))
				reject = true
			}
		}
	}

	subdomainAllowed := true
	if proxyType == "http" || proxyType == "https" {
		subdomainAllowed = false
		if portAllowed && domainAllowed {
			if _, exist := c.Subdomains[user]; exist {
				for _, subdomain := range c.Subdomains[user] {
					if subdomain == userSubdomain {
						subdomainAllowed = true
						break
					}
				}
			} else {
				subdomainAllowed = true
			}
			if !subdomainAllowed {
				portErr = fmt.Errorf("user [%v] subdomain [%v] is not allowed", user, userSubdomain)
				reject = true
			}
		}
	}

	if reject {
		res.Reject = true
		res.RejectReason = portErr.Error()
	} else {
		res.Unchange = true
	}
	return res
}

func StringIndexOf(element string, data []string) int {
	for k, v := range data {
		if element == v {
			return k
		}
	}
	return -1
}
