package policycatalog

import (
	"bufio"
	"os"
	"regexp"
	"strings"
)

// Rule is one allow/deny/escalate rule parsed from Rego (ACP decision object pattern).
type Rule struct {
	ID         string   `json:"id"`
	Decision   string   `json:"decision"`
	Reason     string   `json:"reason"`
	IsDefault  bool     `json:"is_default,omitempty"`
	Priority   int      `json:"priority"`
	Conditions []string `json:"conditions,omitempty"`
	Summary    string   `json:"summary,omitempty"`
}

// Route maps input.tool to a policy bundle (router.rego only).
type Route struct {
	Tool     string `json:"tool"`
	PolicyID string `json:"policy_id"`
	Target   string `json:"target,omitempty"`
}

var (
	reDecisionField = regexp.MustCompile(`"decision"\s*:\s*"([^"]+)"`)
	reReasonField   = regexp.MustCompile(`"reason"\s*:\s*"([^"]+)"`)
	reRouterTool    = regexp.MustCompile(`input\.tool\s*==\s*"([^"]+)"`)
	reRouterDeleg   = regexp.MustCompile(`decision\s*=\s*(\w+)\.decision`)
)

// ParseRules extracts decision rules from an ACP-style Rego module file.
func ParseRules(path string) ([]Rule, error) {
	b, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return ParseRulesFromSource(string(b)), nil
}

// ParseRulesFromSource parses rules from Rego source text.
func ParseRulesFromSource(src string) []Rule {
	var rules []Rule
	lines := strings.Split(src, "\n")
	i := 0
	priority := 0
	for i < len(lines) {
		line := strings.TrimSpace(lines[i])
		isDefault := strings.HasPrefix(line, "default decision")
		if !isDefault && !strings.HasPrefix(line, "decision =") && !strings.HasPrefix(line, "decision :=") {
			i++
			continue
		}
		// Skip router delegation rules (decision = refund.decision {)
		if reRouterDeleg.MatchString(line) {
			i++
			continue
		}
		start := i
		obj, next := readDecisionObject(lines, i)
		if obj == "" {
			i++
			continue
		}
		i = next
		dec, reason := extractDecisionReason(obj)
		if dec == "" {
			continue
		}
		if reason == "" {
			reason = dec
		}
		conds := readConditionBlock(lines, i)
		i = skipConditionBlock(lines, i)
		priority++
		rules = append(rules, Rule{
			ID:         reason,
			Decision:   dec,
			Reason:     reason,
			IsDefault:  isDefault,
			Priority:   priority,
			Conditions: conds,
			Summary:    humanSummary(reason, isDefault),
		})
		_ = start
	}
	return rules
}

// ParseRoutes extracts tool → bundle routes from router.rego.
func ParseRoutes(path string) ([]Route, error) {
	b, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return ParseRoutesFromSource(string(b)), nil
}

func ParseRoutesFromSource(src string) []Route {
	var routes []Route
	sc := bufio.NewScanner(strings.NewReader(src))
	var pendingTarget string
	for sc.Scan() {
		line := strings.TrimSpace(sc.Text())
		if m := reRouterDeleg.FindStringSubmatch(line); len(m) == 2 {
			pendingTarget = m[1]
			continue
		}
		if pendingTarget != "" {
			if m := reRouterTool.FindStringSubmatch(line); len(m) == 2 {
				pid := "acp/" + pendingTarget
				routes = append(routes, Route{
					Tool:     m[1],
					PolicyID: pid,
					Target:   pendingTarget + ".decision",
				})
				pendingTarget = ""
			}
		}
	}
	return routes
}

func readDecisionObject(lines []string, start int) (object string, next int) {
	var b strings.Builder
	depth := 0
	started := false
	for i := start; i < len(lines); i++ {
		line := lines[i]
		for _, ch := range line {
			if ch == '{' {
				depth++
				started = true
			}
			if started {
				b.WriteRune(ch)
			}
			if ch == '}' {
				depth--
				if started && depth == 0 {
					return b.String(), i + 1
				}
			}
		}
		if started && depth > 0 {
			b.WriteByte('\n')
		}
	}
	return "", start + 1
}

func readConditionBlock(lines []string, start int) []string {
	if start >= len(lines) {
		return nil
	}
	trim := strings.TrimSpace(lines[start])
	if !strings.HasPrefix(trim, "{") {
		return nil
	}
	var conds []string
	depth := 0
	for i := start; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		for _, ch := range line {
			if ch == '{' {
				depth++
			}
			if ch == '}' {
				depth--
				if depth == 0 {
					return conds
				}
				continue
			}
		}
		if depth > 0 {
			inner := strings.TrimSpace(strings.Trim(line, "{}"))
			if inner != "" && !strings.HasPrefix(inner, "decision") {
				conds = append(conds, inner)
			}
		}
	}
	return conds
}

func skipConditionBlock(lines []string, start int) int {
	if start >= len(lines) {
		return start
	}
	trim := strings.TrimSpace(lines[start])
	if !strings.HasPrefix(trim, "{") {
		return start
	}
	depth := 0
	for i := start; i < len(lines); i++ {
		for _, ch := range lines[i] {
			if ch == '{' {
				depth++
			}
			if ch == '}' {
				depth--
				if depth == 0 {
					return i + 1
				}
			}
		}
	}
	return start
}

func extractDecisionReason(obj string) (decision, reason string) {
	if m := reDecisionField.FindStringSubmatch(obj); len(m) == 2 {
		decision = m[1]
	}
	if m := reReasonField.FindStringSubmatch(obj); len(m) == 2 {
		reason = m[1]
	}
	return decision, reason
}

func humanSummary(reason string, isDefault bool) string {
	if isDefault {
		return "Default when no other rule matches"
	}
	return strings.ReplaceAll(reason, "_", " ")
}

func domainFromPackage(pkg string) string {
	if pkg == "acp" {
		return "router"
	}
	if strings.HasPrefix(pkg, "acp.") {
		return strings.TrimPrefix(pkg, "acp.")
	}
	return pkg
}
