package cluster

import (
	"context"
	"net/netip"
	"os"
	"strconv"
	"strings"
	"time"

	"darvaza.org/core"
)

// LookupNetIP uses the DNS Resolver to get the public addresses associated
// to a Machine
func (m *Machine) LookupNetIP(timeout time.Duration) ([]netip.Addr, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)

	defer cancel()

	return m.zone.zones.resolver.LookupNetIP(ctx, "ip", m.FullName())
}

// UpdatePublicAddresses uses the DNS Resolver to set Machine.PublicAddresses
func (m *Machine) UpdatePublicAddresses() error {
	addrs, err := m.LookupNetIP(2 * time.Second)
	if err != nil {
		return err
	}

	m.PublicAddresses = addrs
	return nil
}

func (m *Machine) init() error {
	if err := m.setID(); err != nil {
		return core.Wrap(err, m.Name)
	}

	for i := 0; i < RingsCount; i++ {
		if err := m.tryReadWireguardKeys(i); err != nil {
			return core.Wrap(err, m.Name)
		}
	}

	return nil
}

func (m *Machine) setID() error {
	zoneName := m.zone.Name

	l := len(zoneName)
	switch {
	case len(m.Name) < l+2:
		return ErrInvalidName
	case !strings.HasPrefix(m.Name, zoneName):
		return ErrInvalidName
	case m.Name[l] != '-':
		return ErrInvalidName
	}

	suffix := m.Name[l+1:]
	id, err := strconv.ParseInt(suffix, 10, 8)
	if err != nil {
		return err
	}

	m.ID = int(id)
	return nil
}

// scan is called once we know about all zones and machine names
func (m *Machine) scan(_ *ScanOptions) error {
	for i := 0; i < RingsCount; i++ {
		if err := m.tryApplyWireguardConfig(i); err != nil {
			m.error(err).
				WithField("subsystem", "wireguard").
				WithField("node", m.Name).
				WithField("ring", i).
				Print()
			return err
		}
	}

	return m.loadInactive()
}

func (m *Machine) loadInactive() error {
	data, err := m.ReadLines("region")
	switch {
	case os.IsNotExist(err):
		// no file
		return nil
	case err != nil:
		// read error
		return err
	default:
		// look for "none"
		for _, r := range data {
			switch r {
			case "none":
				m.Inactive = true
			default:
				m.Inactive = false
			}
		}
		return nil
	}
}

// scanWrapUp is called once all machines have been scanned
func (m *Machine) scanWrapUp(opts *ScanOptions) error {
	for _, ri := range m.Rings {
		if err := m.setRingDefaults(ri); err != nil {
			m.error(err).
				WithField("subsystem", "wireguard").
				WithField("node", m.Name).
				WithField("ring", ri.Ring).
				Print()
			return err
		}
	}

	if !opts.DontResolvePublicAddresses {
		return m.UpdatePublicAddresses()
	}

	return nil
}