package wgfunc

import (
	"errors"
	"fmt"
	"golang.zx2c4.com/wireguard/wgctrl"
	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)


type WireGuard struct {
	Device

	wgConfig wgtypes.Config
	wgClient *wgctrl.Client
	wgUpdate sync.Mutex

	port int          //Wireguard WireGuard Port
	privateKey string //Wireguard WireGuard Private Key
}

type WireGuardConfig struct{
	Name string
	IPRanges []string
	Port int          //Wireguard WireGuard Port
	PrivateKey string //Wireguard WireGuard Private Key
}

func NewWireGuard(config WireGuardConfig) (WireGuard, error){
	wireguard := WireGuard{
		port: config.Port,
		privateKey: config.PrivateKey,
	}

	wireguard.name = config.Name
	wireguard.iPRanges = config.IPRanges

	return wireguard, nil
}

type PeerConfig struct {
	PublicKey string
	PreSharedKey string
	AllowedIPs []string
	Endpoint string
}

func (s *WireGuard) Down() (error) {
	return s.removeNetworkDevice()
}

func (s *WireGuard) Up() (error)  {
	err := s.createNetworkDevice()
	if err != nil {
		return err
	}

	err = s.addIPToNetwork()
	if err != nil{
		err2 := s.removeNetworkDevice()
		if err2 != nil {
			return fmt.Errorf("Error deleting Network Device %w because of IP range error %s.", err2, err)
		}
		return err
	}

	err = s.wgSetup()
	if err != nil{
		err2 := s.removeNetworkDevice()
		if err2 != nil {
			return fmt.Errorf("Error deleting Network Device %w because of Wiregaurd error %s.", err2, err)
		}
		return err
	}

	err = s.upNetworkDevice()
	if err != nil{ //todo: test
		err2 := s.removeNetworkDevice()
		if err2 != nil {
			return fmt.Errorf("Error deleting Network Device %w because of Wiregaurd error %s.", err2, err)
		}
		return err
	}

	return nil
}

func (s *WireGuard) AddPeer(config PeerConfig) (error) {
	s.wgUpdate.Lock()
	defer s.wgUpdate.Unlock()
	if s.wgConfig.PrivateKey == nil {
		return errors.New("server not up")
	}

	client, err := wgctrl.New()
	if err != nil {
		return err
	}

	pubKey, err := wgtypes.ParseKey(config.PublicKey)
	if err != nil {
		return err
	}

	var ipList []net.IPNet

	for _, ip := range config.AllowedIPs {
		_, net, err := net.ParseCIDR(ip)
		if err != nil {
			return err
		}
		ipList = append(ipList, *net)
	}

	d := 30 * time.Second

	var endpoint *net.UDPAddr
	if config.Endpoint != "" {
		//todo: allow hostnames
		details := strings.Split(config.Endpoint, ":")
		ip := net.ParseIP(details[0])
		if ip == nil{
			return errors.New("Invalide endpoint ip")
		}

		port, err := strconv.Atoi(details[1])
		if err != nil {
			return fmt.Errorf("Cant convert port to int: %w", err)
		}

		udpaddr := net.UDPAddr{
			IP:   ip,
			Port: port,
		}
		endpoint = &udpaddr
	}

	var preKey *wgtypes.Key
	if config.PreSharedKey != "" {
		presharedKey, err := wgtypes.ParseKey(config.PreSharedKey )
		if err != nil {
			return err
		}
		preKey = &presharedKey
	}



	peerConfig := wgtypes.PeerConfig{
		PublicKey:                   pubKey,
		Remove:                      false,
		UpdateOnly:                  false,
		PresharedKey:                preKey,
		Endpoint:                    endpoint,
		PersistentKeepaliveInterval: &d,
		ReplaceAllowedIPs:           true,
		AllowedIPs:                  ipList,
	}

	s.wgConfig.Peers = append(s.wgConfig.Peers, peerConfig)

	err = client.ConfigureDevice(s.name, s.wgConfig)
	if err != nil {
		return err
	}

	return nil
}

func (s *WireGuard) RemovePeer(publicKey string) (error) {
	s.wgUpdate.Lock()
	defer s.wgUpdate.Unlock()

	if s.wgConfig.PrivateKey == nil {
		return errors.New("server not up")
	}

	client, err := wgctrl.New()
	if err != nil {
		return err
	}

	var newlist []wgtypes.PeerConfig

	for _ , peer := range s.wgConfig.Peers {
		if peer.PublicKey.String() != publicKey {
			newlist = append(newlist, peer)
		}
	}

	s.wgConfig.Peers = newlist
	err = client.ConfigureDevice(s.name, s.wgConfig)
	if err != nil {
		return err
	}
	return nil
}


func (s *WireGuard) wgSetup() (error) {
	if s.wgConfig.PrivateKey != nil {
		return errors.New("Wireguard already setedup")
	}

	client, err := wgctrl.New()
	if err != nil {
		return err
	}
	s.wgClient = client

	privkey, err := wgtypes.ParseKey(s.privateKey)
	if err != nil {
		return err
	}

	var port *int

	if s.port > 0 {
		port = &s.port
	}


	s.wgConfig = wgtypes.Config{
		PrivateKey:   &privkey,
		ListenPort:   port,
		FirewallMark: nil,
		ReplacePeers: true,
		Peers:        nil,
	}

	s.wgClient.ConfigureDevice(s.name, s.wgConfig)

	return nil
}

