package main

import (
	"net"
	"os"
	"path/filepath"

	"github.com/rs/zerolog/log"
	"golang.zx2c4.com/wireguard/wgctrl"
	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"

	"novit.tech/direktil/pkg/config"
)

func setupVPN(vpn config.VPNDef, localGenDir string) {
	log := log.With().Str("vpn", vpn.Name).Logger()

	log.Info().Msg("VPN: setting up")

	vpnDir := filepath.Join(localGenDir, vpn.Name)
	os.MkdirAll(vpnDir, 0750)

	logMsg := log.Info()
	if vpn.ListenPort != nil {
		logMsg.Int("ListenPort", *vpn.ListenPort)
	}

	// public/private key
	keyFile := filepath.Join(vpnDir, "key")
	keyBytes, err := os.ReadFile(keyFile)
	if os.IsNotExist(err) {
		key, err := wgtypes.GeneratePrivateKey()
		if err != nil {
			fatalf("failed to generate VPN key: %v", err)
		}

		keyBytes = []byte(key.String())

		os.WriteFile(keyFile, keyBytes, 0600)
	} else if err != nil {
		fatalf("failed to read VPN key: %v", err)
	}

	key, err := wgtypes.ParseKey(string(keyBytes))
	if err != nil {
		fatalf("bad VPN key: %v", err)
	}

	logMsg.Stringer("PublicKey", key.PublicKey())

	// pre-shared key
	pskeyFile := filepath.Join(vpnDir, "pskey")
	pskeyBytes, err := os.ReadFile(pskeyFile)
	if os.IsNotExist(err) {
		key, err := wgtypes.GenerateKey()
		if err != nil {
			fatalf("failed to generate VPN pre-shared key: %v", err)
		}

		pskeyBytes = []byte(key.String())

		os.WriteFile(pskeyFile, pskeyBytes, 0600)
	} else if err != nil {
		fatalf("failed to read VPN pre-shared key: %v", err)
	}

	pskey, err := wgtypes.ParseKey(string(pskeyBytes))
	if err != nil {
		fatalf("bad VPN pre-shared key: %v", err)
	}

	{
		keyStr := key.String()
		logMsg.Str("PresharedKey", keyStr[0:4]+"..."+keyStr[len(keyStr)-4:])
	}

	// setup interface
	cfg := wgtypes.Config{
		PrivateKey: &key,
		ListenPort: vpn.ListenPort,
		Peers:      make([]wgtypes.PeerConfig, 0, len(vpn.Peers)),
	}

	for idx, vpnPeer := range vpn.Peers {
		vpnPeer := vpnPeer

		wgPeer := wgtypes.PeerConfig{
			Endpoint:   vpnPeer.Endpoint,
			AllowedIPs: make([]net.IPNet, 0, len(vpnPeer.AllowedIPs)),

			PersistentKeepaliveInterval: &vpnPeer.KeepAlive,
		}

		if vpnPeer.WithPreSharedKey {
			wgPeer.PresharedKey = &pskey
		}

		pubkey, err := wgtypes.ParseKey(vpnPeer.PublicKey)
		if err != nil {
			fatalf("bad VPN peer[%d] public key: %v", idx, err)
		}

		wgPeer.PublicKey = pubkey

		for _, ipnetStr := range vpnPeer.AllowedIPs {
			_, ipnet, err := net.ParseCIDR(ipnetStr)
			if err != nil {
				fatalf("bad IP/net: %q: %v", ipnetStr, err)
			}

			wgPeer.AllowedIPs = append(wgPeer.AllowedIPs, *ipnet)
		}

		cfg.Peers = append(cfg.Peers, wgPeer)
	}

	wg, err := wgctrl.New()
	if err != nil {
		fatalf("failed to setup WireGuard client: %v", err)
	}
	defer wg.Close()

	log.Info().Strs("ips", vpn.IPs).Msg("VPN: creating interface")
	run("ip", "link", "add", vpn.Name, "type", "wireguard")

	for _, ip := range vpn.IPs {
		run("ip", "addr", "add", ip, "dev", vpn.Name)
	}

	logMsg.Msg("VPN: configuring interface")
	err = wg.ConfigureDevice(vpn.Name, cfg)
	if err != nil {
		fatalf("failed to setup VPN %s: %v", vpn.Name, err)
	}

	run("ip", "link", "set", vpn.Name, "up")
}
