package settings

import (
	"fmt"
	"strings"

	"github.com/qdm12/gluetun/internal/constants/providers"
	"github.com/qdm12/gluetun/internal/constants/vpn"
	"github.com/qdm12/gosettings"
	"github.com/qdm12/gosettings/reader"
	"github.com/qdm12/gosettings/validate"
	"github.com/qdm12/gotree"
)

// Provider contains settings specific to a VPN provider.
type Provider struct {
	// Name is the VPN service provider name.
	// It cannot be the empty string in the internal state.
	Name string `json:"name"`
	// ServerSelection is the settings to
	// select the VPN server.
	ServerSelection ServerSelection `json:"server_selection"`
	// PortForwarding is the settings about port forwarding.
	PortForwarding PortForwarding `json:"port_forwarding"`
}

// TODO v4 remove pointer for receiver (because of Surfshark).
func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGetter, warner Warner) (err error) {
	// Validate Name
	var validNames []string
	if vpnType == vpn.OpenVPN {
		validNames = providers.AllWithCustom()
		validNames = append(validNames, "pia") // Retro-compatibility
	} else { // Wireguard
		validNames = []string{
			providers.Airvpn,
			providers.Custom,
			providers.Fastestvpn,
			providers.Ivpn,
			providers.Mullvad,
			providers.Nordvpn,
			providers.Protonvpn,
			providers.Surfshark,
			providers.Windscribe,
		}
	}
	if err = validate.IsOneOf(p.Name, validNames...); err != nil {
		return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
	}

	err = p.ServerSelection.validate(p.Name, filterChoicesGetter, warner)
	if err != nil {
		return fmt.Errorf("server selection: %w", err)
	}

	err = p.PortForwarding.Validate(p.Name)
	if err != nil {
		return fmt.Errorf("port forwarding: %w", err)
	}

	return nil
}

func (p *Provider) copy() (copied Provider) {
	return Provider{
		Name:            p.Name,
		ServerSelection: p.ServerSelection.copy(),
		PortForwarding:  p.PortForwarding.Copy(),
	}
}

func (p *Provider) overrideWith(other Provider) {
	p.Name = gosettings.OverrideWithComparable(p.Name, other.Name)
	p.ServerSelection.overrideWith(other.ServerSelection)
	p.PortForwarding.OverrideWith(other.PortForwarding)
}

func (p *Provider) setDefaults() {
	p.Name = gosettings.DefaultComparable(p.Name, providers.PrivateInternetAccess)
	p.PortForwarding.setDefaults()
	p.ServerSelection.setDefaults(p.Name, *p.PortForwarding.Enabled)
}

func (p Provider) String() string {
	return p.toLinesNode().String()
}

func (p Provider) toLinesNode() (node *gotree.Node) {
	node = gotree.New("VPN provider settings:")
	node.Appendf("Name: %s", p.Name)
	node.AppendNode(p.ServerSelection.toLinesNode())
	node.AppendNode(p.PortForwarding.toLinesNode())
	return node
}

func (p *Provider) read(r *reader.Reader, vpnType string) (err error) {
	p.Name = readVPNServiceProvider(r, vpnType)

	err = p.ServerSelection.read(r, p.Name, vpnType)
	if err != nil {
		return fmt.Errorf("server selection: %w", err)
	}

	err = p.PortForwarding.read(r)
	if err != nil {
		return fmt.Errorf("port forwarding: %w", err)
	}

	return nil
}

func readVPNServiceProvider(r *reader.Reader, vpnType string) (vpnProvider string) {
	vpnProvider = r.String("VPN_SERVICE_PROVIDER", reader.RetroKeys("VPNSP"))
	if vpnProvider == "" {
		if vpnType != vpn.Wireguard && r.Get("OPENVPN_CUSTOM_CONFIG") != nil {
			// retro compatibility
			return providers.Custom
		}
		return ""
	}

	vpnProvider = strings.ToLower(vpnProvider)
	if vpnProvider == "pia" { // retro compatibility
		return providers.PrivateInternetAccess
	}

	return vpnProvider
}
