package wireguard

import (
	"sync"

	"github.com/pkg/errors"
	"golang.zx2c4.com/wireguard/wgctrl"
	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)

// Manager offers a synchronized management interface to the real WireGuard interface.
type Manager struct {
	Cfg *Config
	wg  *wgctrl.Client
	mux sync.RWMutex
}

func (m *Manager) Init() error {
	var err error
	m.wg, err = wgctrl.New()
	if err != nil {
		return errors.Wrap(err, "could not create WireGuard client")
	}

	return nil
}

func (m *Manager) GetDeviceInfo(device string) (*wgtypes.Device, error) {
	dev, err := m.wg.Device(device)
	if err != nil {
		return nil, errors.Wrap(err, "could not get WireGuard device")
	}

	return dev, nil
}

func (m *Manager) GetPeerList(device string) ([]wgtypes.Peer, error) {
	m.mux.RLock()
	defer m.mux.RUnlock()

	dev, err := m.wg.Device(device)
	if err != nil {
		return nil, errors.Wrap(err, "could not get WireGuard device")
	}

	return dev.Peers, nil
}

func (m *Manager) GetPeer(device string, pubKey string) (*wgtypes.Peer, error) {
	m.mux.RLock()
	defer m.mux.RUnlock()

	publicKey, err := wgtypes.ParseKey(pubKey)
	if err != nil {
		return nil, errors.Wrap(err, "invalid public key")
	}

	peers, err := m.GetPeerList(device)
	if err != nil {
		return nil, errors.Wrap(err, "could not get WireGuard peers")
	}

	for _, peer := range peers {
		if peer.PublicKey == publicKey {
			return &peer, nil
		}
	}

	return nil, errors.Errorf("could not find WireGuard peer: %s", pubKey)
}

func (m *Manager) AddPeer(device string, cfg wgtypes.PeerConfig) error {
	m.mux.Lock()
	defer m.mux.Unlock()

	err := m.wg.ConfigureDevice(device, wgtypes.Config{Peers: []wgtypes.PeerConfig{cfg}})
	if err != nil {
		return errors.Wrap(err, "could not configure WireGuard device")
	}

	return nil
}

func (m *Manager) UpdatePeer(device string, cfg wgtypes.PeerConfig) error {
	m.mux.Lock()
	defer m.mux.Unlock()

	cfg.UpdateOnly = true
	err := m.wg.ConfigureDevice(device, wgtypes.Config{Peers: []wgtypes.PeerConfig{cfg}})
	if err != nil {
		return errors.Wrap(err, "could not configure WireGuard device")
	}

	return nil
}

func (m *Manager) RemovePeer(device string, pubKey string) error {
	m.mux.Lock()
	defer m.mux.Unlock()

	publicKey, err := wgtypes.ParseKey(pubKey)
	if err != nil {
		return errors.Wrap(err, "invalid public key")
	}

	peer := wgtypes.PeerConfig{
		PublicKey: publicKey,
		Remove:    true,
	}

	err = m.wg.ConfigureDevice(device, wgtypes.Config{Peers: []wgtypes.PeerConfig{peer}})
	if err != nil {
		return errors.Wrap(err, "could not configure WireGuard device")
	}

	return nil
}

func (m *Manager) UpdateDevice(device string, cfg wgtypes.Config) error {
	return m.wg.ConfigureDevice(device, cfg)
}
