package settings

import (
	"fmt"
	"os"
	"time"

	"github.com/qdm12/gosettings"
	"github.com/qdm12/gotree"
	"github.com/qdm12/govalid/address"
)

// HTTPProxy contains settings to configure the HTTP proxy.
type HTTPProxy struct {
	// User is the username to use for the HTTP proxy.
	// It cannot be nil in the internal state.
	User *string
	// Password is the password to use for the HTTP proxy.
	// It cannot be nil in the internal state.
	Password *string
	// ListeningAddress is the listening address
	// of the HTTP proxy server.
	// It cannot be the empty string in the internal state.
	ListeningAddress string
	// Enabled is true if the HTTP proxy server should run,
	// and false otherwise. It cannot be nil in the
	// internal state.
	Enabled *bool
	// Stealth is true if the HTTP proxy server should hide
	// each request has been proxied to the destination.
	// It cannot be nil in the internal state.
	Stealth *bool
	// Log is true if the HTTP proxy server should log
	// each request/response. It cannot be nil in the
	// internal state.
	Log *bool
	// ReadHeaderTimeout is the HTTP header read timeout duration
	// of the HTTP server. It defaults to 1 second if left unset.
	ReadHeaderTimeout time.Duration
	// ReadTimeout is the HTTP read timeout duration
	// of the HTTP server. It defaults to 3 seconds if left unset.
	ReadTimeout time.Duration
}

func (h HTTPProxy) validate() (err error) {
	// Do not validate user and password

	uid := os.Getuid()
	err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
	if err != nil {
		return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
	}

	return nil
}

func (h *HTTPProxy) copy() (copied HTTPProxy) {
	return HTTPProxy{
		User:              gosettings.CopyPointer(h.User),
		Password:          gosettings.CopyPointer(h.Password),
		ListeningAddress:  h.ListeningAddress,
		Enabled:           gosettings.CopyPointer(h.Enabled),
		Stealth:           gosettings.CopyPointer(h.Stealth),
		Log:               gosettings.CopyPointer(h.Log),
		ReadHeaderTimeout: h.ReadHeaderTimeout,
		ReadTimeout:       h.ReadTimeout,
	}
}

// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
	h.User = gosettings.MergeWithPointer(h.User, other.User)
	h.Password = gosettings.MergeWithPointer(h.Password, other.Password)
	h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress)
	h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled)
	h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth)
	h.Log = gosettings.MergeWithPointer(h.Log, other.Log)
	h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
	h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
}

// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
	h.User = gosettings.OverrideWithPointer(h.User, other.User)
	h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
	h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
	h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
	h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
	h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
	h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
	h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
}

func (h *HTTPProxy) setDefaults() {
	h.User = gosettings.DefaultPointer(h.User, "")
	h.Password = gosettings.DefaultPointer(h.Password, "")
	h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888")
	h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
	h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
	h.Log = gosettings.DefaultPointer(h.Log, false)
	const defaultReadHeaderTimeout = time.Second
	h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
	const defaultReadTimeout = 3 * time.Second
	h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
}

func (h HTTPProxy) String() string {
	return h.toLinesNode().String()
}

func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
	node = gotree.New("HTTP proxy settings:")
	node.Appendf("Enabled: %s", gosettings.BoolToYesNo(h.Enabled))
	if !*h.Enabled {
		return node
	}

	node.Appendf("Listening address: %s", h.ListeningAddress)
	node.Appendf("User: %s", *h.User)
	node.Appendf("Password: %s", gosettings.ObfuscateKey(*h.Password))
	node.Appendf("Stealth mode: %s", gosettings.BoolToYesNo(h.Stealth))
	node.Appendf("Log: %s", gosettings.BoolToYesNo(h.Log))
	node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
	node.Appendf("Read timeout: %s", h.ReadTimeout)

	return node
}
