package rules

import (
	"net/netip"
	"slices"
	"strings"

	"github.com/AdguardTeam/urlfilter/internal/ufnet"
)

// hostRuleDelimiter is the default delimiter for [HostRule] parsing.
const hostRuleDelimiter = " \t"

// HostRule is a structure for simple host-level rules, i.e. /etc/hosts syntax.
//
// See http://man7.org/linux/man-pages/man5/hosts.5.html.
// It also supports "just domain" syntax; in that case, the IP will be set to
// 0.0.0.0.
//
// TODO(a.garipov):  Consider using [hostsfile.Record].
type HostRule struct {
	// IP is the address of the rule.
	IP netip.Addr

	// text is the original text of the rule.
	text string

	// Hostnames is the slice of hostnames associated with IP.
	Hostnames []string

	// id is the identifier of the filter, containing the rule.
	id ListID
}

// splitNextByWhitespace splits the given string at the first entry of a space
// or tab character, returning both parts of the result.  Unlike the
// [strings.Cut], this function also trims repeated delimiter characters from
// both result strings.
func splitNextByWhitespace(s string) (before, after string) {
	s = strings.TrimLeft(s, hostRuleDelimiter)

	idx := strings.IndexAny(s, hostRuleDelimiter)
	if idx == -1 {
		return s, ""
	}

	before = s[:idx]
	after = strings.TrimLeft(s[idx:], hostRuleDelimiter)

	return before, after
}

// NewHostRule parses the rule and creates a new *HostRule.
func NewHostRule(ruleText string, id ListID) (h *HostRule, err error) {
	h = &HostRule{
		text: ruleText,
		id:   id,
	}

	// Strip comments.
	commentIndex := strings.IndexByte(ruleText, '#')
	if commentIndex > 0 {
		ruleText = ruleText[0 : commentIndex-1]
	}

	first, ruleText := splitNextByWhitespace(ruleText)
	if len(ruleText) == 0 {
		if !ufnet.IsDomainName(first) {
			return nil, &RuleSyntaxError{
				msg:      "invalid syntax",
				ruleText: ruleText,
			}
		}

		h.Hostnames = append(h.Hostnames, first)
		h.IP = netip.IPv4Unspecified()
	} else {
		h.IP, err = netip.ParseAddr(first)
		if err != nil {
			return nil, &RuleSyntaxError{
				msg:      err.Error(),
				ruleText: ruleText,
			}
		}

		var host string
		for len(ruleText) != 0 {
			host, ruleText = splitNextByWhitespace(ruleText)
			h.Hostnames = append(h.Hostnames, host)
		}
	}

	return h, nil
}

// type check
var _ Rule = (*HostRule)(nil)

// Text implements the [Rule] interface for *HostRule.
func (f *HostRule) Text() (s string) {
	return f.text
}

// GetFilterListID implements the [Rule] interface for *HostRule.
func (f *HostRule) GetFilterListID() (id ListID) {
	return f.id
}

// String returns original rule text.
func (f *HostRule) String() (s string) {
	return f.text
}

// Match returns true if this filtering rule matches the specified hostname.
func (f *HostRule) Match(hostname string) (ok bool) {
	return slices.Contains(f.Hostnames, hostname)
}
