package firewall

import (
	"fmt"
	"strings"

	"github.com/bettercap/bettercap/v2/core"
	"github.com/bettercap/bettercap/v2/network"
)

type WindowsFirewall struct {
	iface        *network.Endpoint
	forwarding   bool
	redirections map[string]*Redirection
}

func Make(iface *network.Endpoint) FirewallManager {
	firewall := &WindowsFirewall{
		iface:        iface,
		forwarding:   false,
		redirections: make(map[string]*Redirection, 0),
	}

	firewall.forwarding = firewall.IsForwardingEnabled()

	return firewall
}

func (f WindowsFirewall) IsForwardingEnabled() bool {
	if out, err := core.Exec("netsh", []string{"interface", "ipv4", "dump"}); err != nil {
		fmt.Printf("%s\n", err)
		return false
	} else {
		return strings.Contains(out, "forwarding=enabled")
	}
}

func (f WindowsFirewall) EnableForwarding(enabled bool) error {
	v := "enabled"
	if enabled == false {
		v = "disabled"
	}

	if _, err := core.Exec("netsh", []string{"interface", "ipv4", "set", "interface", fmt.Sprintf("%d", f.iface.Index), fmt.Sprintf("forwarding=\"%s\"", v)}); err != nil {
		return err
	}

	return nil
}

func (f WindowsFirewall) generateRule(r *Redirection, enabled bool) []string {
	// https://stackoverflow.com/questions/24646165/netsh-port-forwarding-from-local-port-to-local-port-not-working
	rule := []string{
		fmt.Sprintf("listenport=%d", r.SrcPort),
	}

	if enabled {
		rule = append(rule, fmt.Sprintf("connectport=%d", r.DstPort))
		rule = append(rule, fmt.Sprintf("connectaddress=%s", r.DstAddress))
		rule = append(rule, fmt.Sprintf("protocol=%s", r.Protocol))
	}

	return rule
}

func (f *WindowsFirewall) AllowPort(port int, address string, proto string, allow bool) error {
	ruleName := fmt.Sprintf("bettercap-rule-%s-%s-%d", address, proto, port)
	nameField := fmt.Sprintf(`name="%s"`, ruleName)
	protoField := fmt.Sprintf("protocol=%s", proto)
	// ipField := fmt.Sprintf("lolcalip=%s", address)
	portField := fmt.Sprintf("localport=%d", port)

	cmd := []string{}

	if allow {
		cmd = []string{"advfirewall", "firewall", "add", "rule", nameField, protoField, "dir=in", portField, "action=allow"}
	} else {
		cmd = []string{"advfirewall", "firewall", "delete", "rule", nameField, protoField, portField}
	}

	if _, err := core.Exec("netsh", cmd); err != nil {
		return err
	}

	return nil
}

func (f *WindowsFirewall) EnableRedirection(r *Redirection, enabled bool) error {
	if err := f.AllowPort(r.SrcPort, r.DstAddress, r.Protocol, enabled); err != nil {
		return err
	} else if err := f.AllowPort(r.DstPort, r.DstAddress, r.Protocol, enabled); err != nil {
		return err
	}

	rule := f.generateRule(r, enabled)
	if enabled {
		rule = append([]string{"interface", "portproxy", "add", "v4tov4"}, rule...)
	} else {
		rule = append([]string{"interface", "portproxy", "delete", "v4tov4"}, rule...)
	}

	if _, err := core.Exec("netsh", rule); err != nil {
		return err
	}

	return nil
}

func (f WindowsFirewall) Restore() {
	for _, r := range f.redirections {
		if err := f.EnableRedirection(r, false); err != nil {
			fmt.Printf("%s", err)
		}
	}

	if err := f.EnableForwarding(f.forwarding); err != nil {
		fmt.Printf("%s", err)
	}
}
