package forwarding

import (
	"fmt"
	"strings"

	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/1Panel-dev/1Panel/agent/global"
	"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)

type firewalldCommandRunner interface {
	Run(name string, args ...string) error
	RunWithStdout(name string, args ...string) (string, error)
}

type defaultFirewalldCommandRunner struct{}

func (defaultFirewalldCommandRunner) Run(name string, args ...string) error {
	return cmd.NewCommandMgr().Run(name, args...)
}

func (defaultFirewalldCommandRunner) RunWithStdout(name string, args ...string) (string, error) {
	return cmd.NewCommandMgr().RunWithStdout(name, args...)
}

type firewalldAdapter struct {
	runner firewalldCommandRunner
}

func newFirewalldAdapter() *firewalldAdapter {
	return &firewalldAdapter{runner: defaultFirewalldCommandRunner{}}
}

func (f *firewalldAdapter) Name() string {
	return "firewalld"
}

func (f *firewalldAdapter) List() ([]Rule, error) {
	if err := f.Enable(); err != nil {
		global.LOG.Errorf("init port forward failed, err: %v", err)
	}
	stdout, err := f.runner.RunWithStdout("firewall-cmd", "--zone=public", "--list-forward-ports")
	if err != nil {
		return nil, err
	}
	return parseFirewalldRules(stdout), nil
}

func parseFirewalldRules(stdout string) []Rule {
	var rules []Rule
	for _, line := range strings.Split(stdout, "\n") {
		line = strings.TrimSpace(line)
		parts := strings.Split(line, ":")
		if len(parts) < 4 {
			continue
		}
		if parts[3] == "toaddr=" {
			parts[3] = "127.0.0.1"
		}
		rules = append(rules, Rule{
			Port:       strings.TrimPrefix(parts[0], "port="),
			Protocol:   strings.TrimPrefix(parts[1], "proto="),
			TargetIP:   strings.TrimPrefix(parts[3], "toaddr="),
			TargetPort: strings.TrimPrefix(parts[2], "toport="),
		})
	}
	return rules
}

func (f *firewalldAdapter) Operate(rule Rule, operation string) error {
	if cmd.CheckIllegal(operation, rule.Port, rule.Protocol, rule.TargetIP, rule.TargetPort) {
		return buserr.New("ErrCmdIllegal")
	}
	args := buildFirewalldForwardArgs(rule, operation)
	if err := f.runner.Run("firewall-cmd", args...); err != nil {
		return fmt.Errorf("%s port forward failed, %s", operation, err)
	}
	return f.reload()
}

func buildFirewalldForwardArgs(rule Rule, operation string) []string {
	forwardRule := fmt.Sprintf("--%s-forward-port=port=%s:proto=%s:toport=%s", operation, rule.Port, rule.Protocol, rule.TargetPort)
	if rule.TargetIP != "" && rule.TargetIP != "127.0.0.1" && rule.TargetIP != "localhost" {
		forwardRule = fmt.Sprintf("--%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s", operation, rule.Port, rule.Protocol, rule.TargetIP, rule.TargetPort)
	}
	return []string{"--zone=public", forwardRule, "--permanent"}
}

func (f *firewalldAdapter) Enable() error {
	stdout, err := f.runner.RunWithStdout("firewall-cmd", "--zone=public", "--query-masquerade")
	if err != nil {
		if strings.HasSuffix(strings.TrimSpace(stdout), "no") {
			if err := f.runner.Run("firewall-cmd", "--zone=public", "--add-masquerade", "--permanent"); err != nil {
				return err
			}
			return f.reload()
		}
		return err
	}
	return nil
}

func (f *firewalldAdapter) reload() error {
	if err := f.runner.Run("firewall-cmd", "--reload"); err != nil {
		return fmt.Errorf("reload firewall failed, err: %v", err)
	}
	return nil
}

func (f *firewalldAdapter) InitStatus() (bool, bool) {
	return true, true
}

func (f *firewalldAdapter) Replay() error {
	return nil
}
