package vtun

import (
	"fmt"
	"net"
	"os"
	"strings"

	"github.com/USA-RedDragon/aredn-manager/internal/config"
	"github.com/USA-RedDragon/aredn-manager/internal/db/models"
	"github.com/USA-RedDragon/aredn-manager/internal/utils"
	"gorm.io/gorm"
)

const (
	snippetVtunConf = `# This file is generated by the AREDN Manager.
# Do not edit this file directly.
options {
    port 5525;
    timeout 60;
    syslog daemon;
    ip /sbin/ip;
    firewall /sbin/iptables;
}`

	//nolint:golint,dupword
	snippetVtunConfStandardTunnel = `${CONNECTION_HOST} {
    passwd ${PWD};
    type tun;
    proto tcp;
    device tun${TUN};
    persist yes;
    compress no;
    keepalive yes;
    up {
        ip "addr add ${IP_PLUS_2} peer ${IP_PLUS_1} dev %%";
        ip "link set dev %% up mtu 1450";
        ip "route add ${NET}/30 via ${IP_PLUS_1} mtu 1450";
        firewall "-A FORWARD -i %% -o br0 -d 10.0.0.0/8 -j ACCEPT";
        firewall "-A FORWARD -i %% -o br0 -j REJECT";
        firewall "-A FORWARD -i br0 -o %% -s 10.0.0.0/8 -j ACCEPT";
        firewall "-A FORWARD -i br0 -o %% -j REJECT";
        firewall "-A FORWARD -o %% -p tcp --tcp-flags SYN SYN -j TCPMSS --set-mss 1450";
        ${EXTRA_UP_RULES}
    };
    down {
        ${EXTRA_DOWN_RULES}
        firewall "-D FORWARD -o %% -p tcp --tcp-flags SYN SYN -j TCPMSS --set-mss 1450";
        firewall "-D FORWARD -i %% -o br0 -d 10.0.0.0/8 -j ACCEPT";
        firewall "-D FORWARD -i br0 -o %% -s 10.0.0.0/8 -j ACCEPT";
        firewall "-D FORWARD -i %% -o br0 -j REJECT";
        firewall "-D FORWARD -i br0 -o %% -j REJECT";
        ip "route del ${NET}/30 via ${IP_PLUS_1}";
        ip "link set dev %% down";
        ip "addr del ${IP_PLUS_2} dev %%";
    };
}`

	snippetVtunConfMasquaradeUpRules = `firewall "-t nat -A POSTROUTING -o %% -j SNAT --to-source ${IP_PLUS_2}";
        firewall "-t nat -A POSTROUTING -o %% -p udp --dport 698 -j MASQUERADE";`

	snippetVtunConfMasquaradeDownRules = `firewall "-t nat -D POSTROUTING -o %% -j SNAT --to-source ${IP_PLUS_2}";
        firewall "-t nat -D POSTROUTING -o %% -p udp --dport 698 -j MASQUERADE";`

	snippetVtunConfWireguardUpRules = `firewall "-A FORWARD -i wg0 -o %% -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT";
        firewall "-A FORWARD -i %% -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT";
        firewall "-A FORWARD -i wg0 -o %% -j ACCEPT";
        firewall "-A FORWARD -i %% -o wg0 -j ACCEPT";
        ip "route add ${WG_TAP_PLUS_1}/32 dev wg0";`

	snippetVtunConfWireguardDownRules = `firewall "-D FORWARD -i wg0 -o %% -j ACCEPT";
        firewall "-D FORWARD -i %% -o wg0 -j ACCEPT";
        firewall "-D FORWARD -i wg0 -o %% -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT";
        firewall "-D FORWARD -i %% -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT";`
)

func GenerateAndSave(config *config.Config, db *gorm.DB) error {
	conf := Generate(config, db)
	if conf == "" {
		return fmt.Errorf("failed to generate vtun.conf")
	}

	//nolint:golint,gosec
	return os.WriteFile("/etc/vtundsrv.conf", []byte(conf), 0644)
}

// This file will generate the vtun.conf file
func Generate(config *config.Config, db *gorm.DB) string {
	ret := snippetVtunConf

	tunnels, err := models.ListServerTunnels(db)
	if err != nil {
		return ""
	}

	tun := 50
	for _, tunnel := range tunnels {
		if tunnel.Wireguard {
			continue
		}
		ret += "\n\n"
		// We need to replace shell variables in the template with the actual values
		cpSnippetVtunConfTunnel := snippetVtunConfStandardTunnel
		ip := net.ParseIP(tunnel.IP).To4()
		ipPlus1 := net.IPv4(ip[0], ip[1], ip[2], ip[3]+1)
		ipPlus2 := net.IPv4(ip[0], ip[1], ip[2], ip[3]+2)
		extraUpRules := ""
		extraDownRules := ""
		if config.Masquerade {
			extraUpRules += snippetVtunConfMasquaradeUpRules
			utils.ShellReplace(&extraUpRules, map[string]string{
				"IP_PLUS_2": ipPlus2.String(),
			})
			extraDownRules += snippetVtunConfMasquaradeDownRules
			utils.ShellReplace(&extraDownRules, map[string]string{
				"IP_PLUS_2": ipPlus2.String(),
			})
		}
		if config.WireguardTapAddress != "" {
			wgTapIP := net.ParseIP(config.WireguardTapAddress).To4()
			wgTapIPPlus1 := net.IPv4(wgTapIP[0], wgTapIP[1], wgTapIP[2], wgTapIP[3]+1)
			if extraUpRules != "" {
				//nolint:golint,goconst
				extraUpRules += "\n        "
				extraDownRules += "\n        "
			}
			extraUpRules += snippetVtunConfWireguardUpRules
			extraDownRules += snippetVtunConfWireguardDownRules
			utils.ShellReplace(&extraUpRules, map[string]string{
				"WG_TAP_PLUS_1": wgTapIPPlus1.String(),
			})
			utils.ShellReplace(&extraDownRules, map[string]string{
				"WG_TAP_PLUS_1": wgTapIPPlus1.String(),
			})
		}
		dashedNet := strings.ReplaceAll(tunnel.IP, ".", "-")
		nameLen := len(tunnel.Hostname)
		maxNameLen := 36 - len(dashedNet)
		name := tunnel.Hostname
		if nameLen > maxNameLen {
			name = name[:maxNameLen]
		}
		vtunConnectionHost := fmt.Sprintf("%s-%s", name, dashedNet)
		utils.ShellReplace(
			&cpSnippetVtunConfTunnel,
			map[string]string{
				"CONNECTION_HOST":  vtunConnectionHost,
				"PWD":              tunnel.Password,
				"TUN":              fmt.Sprintf("%d", tun),
				"IP_PLUS_1":        ipPlus1.String(),
				"IP_PLUS_2":        ipPlus2.String(),
				"NET":              tunnel.IP,
				"EXTRA_UP_RULES":   extraUpRules,
				"EXTRA_DOWN_RULES": extraDownRules,
			},
		)
		ret += cpSnippetVtunConfTunnel
		tun++
	}
	return ret
}
