package rulematch

import (
	"net"
	"net/url"
	"strings"

	"github.com/prism/proxy/internal/country"
	"github.com/prism/proxy/internal/store"
)

type Result struct {
	Matched       bool   `json:"matched"`
	Host          string `json:"host"`
	Port          string `json:"port,omitempty"`
	RuleLine      string `json:"rule_line"`
	RuleType      string `json:"rule_type"`
	Payload       string `json:"payload"`
	Target        string `json:"target"`
	TargetLabel   string `json:"target_label"`
	Source        string `json:"source"`
	Priority      int    `json:"priority"`
	SkippedGeo    int    `json:"skipped_geo"`
	Note          string `json:"note,omitempty"`
}

func ParseHost(raw string) (host, port string) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return "", ""
	}
	if !strings.Contains(raw, "://") {
		raw = "http://" + raw
	}
	u, err := url.Parse(raw)
	if err != nil {
		return strings.TrimSpace(raw), ""
	}
	host = u.Hostname()
	port = u.Port()
	if host == "" {
		host = strings.TrimSpace(raw)
	}
	return strings.ToLower(host), port
}

func Match(rules []store.Rule, defaultPolicy, rawURL string, geoReady bool) Result {
	host, port := ParseHost(rawURL)
	if host == "" {
		return Result{Note: "无法解析 URL 或域名"}
	}

	skippedGeo := 0
	for _, r := range rules {
		if !r.Enabled {
			continue
		}
		ruleType := strings.ToUpper(strings.TrimSpace(r.RuleType))
		if ruleType == "MATCH" {
			continue
		}
		if isGeoRule(ruleType) {
			if !geoReady {
				skippedGeo++
				continue
			}
			// Geo rules need Mihomo geodata engine; skip in offline matcher.
			skippedGeo++
			continue
		}
		if matchesRule(ruleType, r.Payload, host, port) {
			line := formatLine(r)
			return Result{
				Matched:     true,
				Host:        host,
				Port:        port,
				RuleLine:    line,
				RuleType:    ruleType,
				Payload:     r.Payload,
				Target:      r.Target,
				TargetLabel: targetLabel(r.Target),
				Source:      r.Source,
				Priority:    r.Priority,
				SkippedGeo:  skippedGeo,
			}
		}
	}

	policy := defaultPolicy
	if policy == "" {
		policy = "DIRECT"
	}
	return Result{
		Matched:     true,
		Host:        host,
		Port:        port,
		RuleLine:    "MATCH," + policy,
		RuleType:    "MATCH",
		Payload:     "",
		Target:      policy,
		TargetLabel: targetLabel(policy),
		Source:      "system",
		Priority:    999999,
		SkippedGeo:  skippedGeo,
		Note:        "未命中任何前置规则，使用默认出站",
	}
}

func isGeoRule(ruleType string) bool {
	switch ruleType {
	case "GEOIP", "GEOSITE":
		return true
	default:
		return false
	}
}

func matchesRule(ruleType, payload, host, port string) bool {
	payload = strings.TrimSpace(payload)
	switch ruleType {
	case "DOMAIN":
		return host == strings.ToLower(payload)
	case "DOMAIN-SUFFIX":
		suffix := strings.ToLower(payload)
		return host == suffix || strings.HasSuffix(host, "."+suffix)
	case "DOMAIN-KEYWORD":
		return strings.Contains(host, strings.ToLower(payload))
	case "IP-CIDR", "IP-CIDR6":
		return matchCIDR(host, payload)
	case "DST-PORT":
		return port != "" && port == strings.TrimSpace(payload)
	case "SRC-PORT":
		return false
	default:
		return false
	}
}

func matchCIDR(host, cidr string) bool {
	ip := net.ParseIP(host)
	if ip == nil {
		return false
	}
	_, network, err := net.ParseCIDR(strings.TrimSpace(cidr))
	if err != nil {
		return false
	}
	return network.Contains(ip)
}

func formatLine(r store.Rule) string {
	if r.Payload == "" {
		return r.RuleType + "," + r.Target
	}
	return r.RuleType + "," + r.Payload + "," + r.Target
}

func targetLabel(target string) string {
	target = strings.TrimSpace(target)
	if code, ok := country.ParseTargetAlias(target); ok {
		return country.Name(code) + "（国家池）"
	}
	if strings.HasPrefix(target, "country_") {
		code := strings.TrimPrefix(target, "country_")
		return country.Name(code) + "（国家池）"
	}
	return target
}
