package settings

import (
	"errors"
	"fmt"
	"net/netip"
	"regexp"

	"github.com/qdm12/dns/pkg/blacklist"
	"github.com/qdm12/gosettings"
	"github.com/qdm12/gotree"
)

// DNSBlacklist is settings for the DNS blacklist building.
type DNSBlacklist struct {
	BlockMalicious       *bool
	BlockAds             *bool
	BlockSurveillance    *bool
	AllowedHosts         []string
	AddBlockedHosts      []string
	AddBlockedIPs        []netip.Addr
	AddBlockedIPPrefixes []netip.Prefix
}

func (b *DNSBlacklist) setDefaults() {
	b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
	b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
	b.BlockSurveillance = gosettings.DefaultPointer(b.BlockSurveillance, true)
}

var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll

var (
	ErrAllowedHostNotValid = errors.New("allowed host is not valid")
	ErrBlockedHostNotValid = errors.New("blocked host is not valid")
)

func (b DNSBlacklist) validate() (err error) {
	for _, host := range b.AllowedHosts {
		if !hostRegex.MatchString(host) {
			return fmt.Errorf("%w: %s", ErrAllowedHostNotValid, host)
		}
	}

	for _, host := range b.AddBlockedHosts {
		if !hostRegex.MatchString(host) {
			return fmt.Errorf("%w: %s", ErrBlockedHostNotValid, host)
		}
	}

	return nil
}

func (b DNSBlacklist) copy() (copied DNSBlacklist) {
	return DNSBlacklist{
		BlockMalicious:       gosettings.CopyPointer(b.BlockMalicious),
		BlockAds:             gosettings.CopyPointer(b.BlockAds),
		BlockSurveillance:    gosettings.CopyPointer(b.BlockSurveillance),
		AllowedHosts:         gosettings.CopySlice(b.AllowedHosts),
		AddBlockedHosts:      gosettings.CopySlice(b.AddBlockedHosts),
		AddBlockedIPs:        gosettings.CopySlice(b.AddBlockedIPs),
		AddBlockedIPPrefixes: gosettings.CopySlice(b.AddBlockedIPPrefixes),
	}
}

func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
	b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
	b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
	b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
	b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
	b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
	b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
	b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}

func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
	b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
	b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
	b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
	b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
	b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
	b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
	b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}

func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
	return blacklist.BuilderSettings{
		BlockMalicious:       *b.BlockMalicious,
		BlockAds:             *b.BlockAds,
		BlockSurveillance:    *b.BlockSurveillance,
		AllowedHosts:         b.AllowedHosts,
		AddBlockedHosts:      b.AddBlockedHosts,
		AddBlockedIPs:        netipAddressesToNetaddrIPs(b.AddBlockedIPs),
		AddBlockedIPPrefixes: netipPrefixesToNetaddrIPPrefixes(b.AddBlockedIPPrefixes),
	}, nil
}

func (b DNSBlacklist) String() string {
	return b.toLinesNode().String()
}

func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
	node = gotree.New("DNS filtering settings:")

	node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
	node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
	node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))

	if len(b.AllowedHosts) > 0 {
		allowedHostsNode := node.Appendf("Allowed hosts:")
		for _, host := range b.AllowedHosts {
			allowedHostsNode.Appendf(host)
		}
	}

	if len(b.AddBlockedHosts) > 0 {
		blockedHostsNode := node.Appendf("Blocked hosts:")
		for _, host := range b.AddBlockedHosts {
			blockedHostsNode.Appendf(host)
		}
	}

	if len(b.AddBlockedIPs) > 0 {
		blockedIPsNode := node.Appendf("Blocked IP addresses:")
		for _, ip := range b.AddBlockedIPs {
			blockedIPsNode.Appendf(ip.String())
		}
	}

	if len(b.AddBlockedIPPrefixes) > 0 {
		blockedIPPrefixesNode := node.Appendf("Blocked IP networks:")
		for _, ipNetwork := range b.AddBlockedIPPrefixes {
			blockedIPPrefixesNode.Appendf(ipNetwork.String())
		}
	}

	return node
}
