package cluster

import (
	"io/fs"
	"path/filepath"

	"darvaza.org/resolver"
	"darvaza.org/slog"
)

// A ScanOption pre-configures the Zones before scanning
type ScanOption func(*Cluster, *ScanOptions) error

// ScanOptions contains flags used by the initial scan
type ScanOptions struct {
	// DontResolvePublicAddresses indicates we shouldn't
	// pre-populate Machine.PublicAddresses during the
	// initial scan
	DontResolvePublicAddresses bool

	// Logger specifies the logger to be used. otherwise
	// the scanner will be mute
	slog.Logger
}

// ResolvePublicAddresses instructs the scanner to use
// the DNS resolver to get PublicAddresses of nodes.
// Default is true
func ResolvePublicAddresses(resolve bool) ScanOption {
	return func(_ *Cluster, opt *ScanOptions) error {
		opt.DontResolvePublicAddresses = !resolve
		return nil
	}
}

// WithLookuper specifies what resolver.Lookuper to use to
// find public addresses
func WithLookuper(h resolver.Lookuper) ScanOption {
	return func(m *Cluster, _ *ScanOptions) error {
		if h == nil {
			return fs.ErrInvalid
		}
		m.resolver = resolver.NewResolver(h)
		return nil
	}
}

// WithResolver specifies what resolver to use to find
// public addresses. if nil is passed, the [net.Resolver] will be used.
// The default is using Cloudflare's 1.1.1.1.
func WithResolver(h resolver.Resolver) ScanOption {
	return func(m *Cluster, _ *ScanOptions) error {
		if h == nil {
			h = resolver.SystemResolver(true)
		}

		m.resolver = h
		return nil
	}
}

// WithLogger specifies what to use for logging
func WithLogger(log slog.Logger) ScanOption {
	return func(m *Cluster, opt *ScanOptions) error {
		if log == nil {
			log = DefaultLogger()
		}

		opt.Logger = log
		m.log = log
		return nil
	}
}

func (m *Cluster) setScanDefaults(opt *ScanOptions) error {
	if m.resolver == nil {
		h := DefaultLookuper()

		if err := WithLookuper(h)(m, opt); err != nil {
			return err
		}
	}

	if opt.Logger == nil {
		if err := WithLogger(nil)(m, opt); err != nil {
			return err
		}
	}

	return nil
}

// NewFromDirectory builds a [Cluster] tree using the given directory
func NewFromDirectory(dir, domain string, opts ...ScanOption) (*Cluster, error) {
	var scanOptions ScanOptions

	dir = filepath.Clean(dir)
	fullPath, err := filepath.Abs(dir)
	if err != nil {
		return nil, err
	}

	sub, err := DirFS(dir)
	if err != nil {
		return nil, err
	}

	m := &Cluster{
		dir:     sub,
		BaseDir: dir,
		Name:    filepath.Base(fullPath),
		Domain:  domain,
	}

	for _, opt := range opts {
		if err := opt(m, &scanOptions); err != nil {
			return nil, err
		}
	}

	if err := m.setScanDefaults(&scanOptions); err != nil {
		return nil, err
	}

	if err := m.scan(&scanOptions); err != nil {
		return nil, err
	}

	return m, nil
}