package service

import (
	"bytes"
	"fmt"
	"os"
	"path"
	"strings"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/app/dto/request"
	"github.com/1Panel-dev/1Panel/agent/app/repo"
	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
	"github.com/1Panel-dev/1Panel/agent/constant"
	"github.com/1Panel-dev/1Panel/agent/utils/files"
	"github.com/1Panel-dev/1Panel/agent/utils/nginx"
	"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
	"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
)

func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) {
	website, err := websiteRepo.GetFirst(repo.WithByID(id))
	if err != nil {
		return nil, err
	}
	includeDir := GetSitePath(website, SiteUpstreamDir)
	fileOp := files.NewFileOp()
	if !fileOp.Stat(includeDir) {
		return nil, nil
	}
	entries, err := os.ReadDir(includeDir)
	if err != nil {
		return nil, err
	}
	var res []dto.NginxUpstream
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()
		if !strings.HasSuffix(name, ".conf") {
			continue
		}
		upstreamName := strings.TrimSuffix(name, ".conf")
		upstream := dto.NginxUpstream{
			Name: upstreamName,
		}
		upstreamPath := path.Join(includeDir, name)
		content, err := fileOp.GetContent(upstreamPath)
		if err != nil {
			return nil, err
		}
		upstream.Content = string(content)
		nginxParser, err := parser.NewParser(upstreamPath)
		if err != nil {
			return nil, err
		}
		config, err := nginxParser.Parse()
		if err != nil {
			return nil, err
		}
		upstreams := config.FindUpstreams()
		for _, up := range upstreams {
			if up.UpstreamName == upstreamName {
				directives := up.GetDirectives()
				for _, d := range directives {
					dName := d.GetName()
					if _, ok := dto.LBAlgorithms[dName]; ok {
						upstream.Algorithm = dName
					}
				}
				upstream.Servers = getNginxUpstreamServers(up.UpstreamServers)
			}
		}
		res = append(res, upstream)
	}
	return res, nil
}

func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error {
	website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
	if err != nil {
		return err
	}
	includeDir := GetSitePath(website, SiteUpstreamDir)
	fileOp := files.NewFileOp()
	if !fileOp.Stat(includeDir) {
		_ = fileOp.CreateDir(includeDir, constant.DirPerm)
	}
	safeName := path.Base(req.Name)
	if safeName != req.Name || strings.Contains(safeName, "..") {
		return buserr.New("ErrInvalidParams")
	}
	filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
	if fileOp.Stat(filePath) {
		return buserr.New("ErrNameIsExist")
	}
	config, err := parser.NewStringParser(string(nginx_conf.Upstream)).Parse()
	if err != nil {
		return err
	}
	config.Block = &components.Block{}
	config.FilePath = filePath
	upstream := components.Upstream{
		UpstreamName: req.Name,
	}
	if req.Algorithm != "default" {
		upstream.UpdateDirective(req.Algorithm, []string{})
	}
	upstream.UpstreamServers = parseUpstreamServers(req.Servers)
	config.Block.Directives = append(config.Block.Directives, &upstream)

	defer func() {
		if err != nil {
			_ = fileOp.DeleteFile(filePath)
		}
	}()

	if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
		return buserr.WithErr("ErrUpdateBuWebsite", err)
	}
	nginxInclude := fmt.Sprintf("/www/sites/%s/upstream/*.conf", website.Alias)
	if err = updateNginxConfig("", []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
		return err
	}
	return nil
}

func (w WebsiteService) UpdateLoadBalance(req request.WebsiteLBUpdate) error {
	website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
	if err != nil {
		return err
	}
	nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
	if err != nil {
		return err
	}
	includeDir := GetSitePath(website, SiteUpstreamDir)
	fileOp := files.NewFileOp()
	safeName := path.Base(req.Name)
	if safeName != req.Name || strings.Contains(safeName, "..") {
		return buserr.New("ErrInvalidParams")
	}
	filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
	if !fileOp.Stat(filePath) {
		return nil
	}
	oldContent, err := fileOp.GetContent(filePath)
	if err != nil {
		return err
	}
	parser, err := parser.NewParser(filePath)
	if err != nil {
		return err
	}
	config, err := parser.Parse()
	if err != nil {
		return err
	}
	upstreams := config.FindUpstreams()
	for _, up := range upstreams {
		if up.UpstreamName == req.Name {
			directives := up.GetDirectives()
			for _, d := range directives {
				dName := d.GetName()
				if _, ok := dto.LBAlgorithms[dName]; ok {
					up.RemoveDirective(dName, nil)
				}
			}
			if req.Algorithm != "default" {
				up.UpdateDirective(req.Algorithm, []string{})
			}
			up.UpstreamServers = parseUpstreamServers(req.Servers)
		}
	}
	if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
		return buserr.WithErr("ErrUpdateBuWebsite", err)
	}
	return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
}

func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error {
	website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
	if err != nil {
		return err
	}
	nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
	if err != nil {
		return err
	}
	proxies, _ := w.GetProxies(website.ID)
	if len(proxies) > 0 {
		for _, proxy := range proxies {
			if strings.HasSuffix(proxy.ProxyPass, fmt.Sprintf("://%s", req.Name)) {
				return buserr.New("ErrProxyIsUsed")
			}
		}
	}

	includeDir := GetSitePath(website, SiteUpstreamDir)
	fileOp := files.NewFileOp()
	safeName := path.Base(req.Name)
	if safeName != req.Name || strings.Contains(safeName, "..") {
		return buserr.New("ErrInvalidParams")
	}
	filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
	if !fileOp.Stat(filePath) {
		return nil
	}
	if err = fileOp.DeleteFile(filePath); err != nil {
		return err
	}
	return opNginx(nginxInstall.ContainerName, constant.NginxReload)
}

func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error {
	website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
	if err != nil {
		return err
	}
	nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
	if err != nil {
		return err
	}
	includeDir := GetSitePath(website, SiteUpstreamDir)
	safeName := path.Base(req.Name)
	if safeName != req.Name || strings.Contains(safeName, "..") {
		return buserr.New("ErrInvalidParams")
	}
	filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
	fileOp := files.NewFileOp()
	oldContent, err := fileOp.GetContent(filePath)
	if err != nil {
		return err
	}
	if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
		return err
	}
	defer func() {
		if err != nil {
			_ = fileOp.WriteFile(filePath, bytes.NewReader(oldContent), constant.DirPerm)
		}
	}()
	return opNginx(nginxInstall.ContainerName, constant.NginxReload)
}
