"""Covenant rule parser.

Parses a rule DSL string into structured Rule objects.

Grammar:
    rules      := rule (';' rule)* ';'?
    rule       := action target (condition)?
    action     := 'permit' | 'forbid' | 'require'
    target     := identifier
    condition  := 'where' comparison
    comparison := field operator value
    operator   := '>' | '<' | '>=' | '<=' | '==' | '!='
"""

from __future__ import annotations

import operator as op
import re
from dataclasses import dataclass, field
from typing import Any

OPERATORS: dict[str, Any] = {
    ">": op.gt,
    "<": op.lt,
    ">=": op.ge,
    "<=": op.le,
    "==": op.eq,
    "!=": op.ne,
}


@dataclass
class Condition:
    """A single condition like 'amount > 500'."""

    field: str
    operator: str
    value: Any

    def evaluate(self, params: dict[str, Any]) -> bool:
        """Check if params satisfy this condition."""
        if self.field not in params:
            return False
        param_val = params[self.field]
        try:
            cmp_val = type(param_val)(self.value)
        except (ValueError, TypeError):
            return False
        return OPERATORS[self.operator](param_val, cmp_val)


@dataclass
class Rule:
    """A parsed covenant rule."""

    action: str  # permit, forbid, require
    target: str  # tool/action name, or special like 'log_all'
    conditions: list[Condition] = field(default_factory=list)

    def matches(self, tool_name: str, tool_args: dict[str, Any]) -> bool:
        """Check if a tool call matches this rule."""
        if self.target != tool_name:
            return False
        if not self.conditions:
            return True
        return all(c.evaluate(tool_args) for c in self.conditions)

    def __str__(self) -> str:
        s = f"{self.action} {self.target}"
        if self.conditions:
            conds = " and ".join(
                f"{c.field} {c.operator} {c.value}" for c in self.conditions
            )
            s += f" where {conds}"
        return s


def _parse_value(raw: str) -> int | float | str:
    """Parse a value string into int, float, or str."""
    try:
        return int(raw)
    except ValueError:
        pass
    try:
        return float(raw)
    except ValueError:
        pass
    return raw.strip("'\"")


_CONDITION_RE = re.compile(
    r"(\w+)\s*(>=|<=|!=|==|>|<)\s*(\S+)"
)


def parse_rules(rules_str: str) -> list[Rule]:
    """Parse a covenant rules string into a list of Rule objects.

    Example:
        >>> parse_rules("permit read; forbid transfer where amount > 500;")
        [Rule(action='permit', target='read'), Rule(action='forbid', target='transfer', ...)]
    """
    rules: list[Rule] = []
    parts = [p.strip() for p in rules_str.split(";") if p.strip()]

    for part in parts:
        tokens = part.split()
        if len(tokens) < 2:
            raise ValueError(f"Invalid rule: '{part}' — expected 'action target [where ...]'")

        action = tokens[0].lower()
        if action not in ("permit", "forbid", "require"):
            raise ValueError(f"Unknown action '{action}' in rule: '{part}'")

        target = tokens[1]
        conditions: list[Condition] = []

        if len(tokens) > 2:
            if tokens[2].lower() != "where":
                raise ValueError(f"Expected 'where' in rule: '{part}'")
            condition_str = " ".join(tokens[3:])
            for match in _CONDITION_RE.finditer(condition_str):
                conditions.append(Condition(
                    field=match.group(1),
                    operator=match.group(2),
                    value=_parse_value(match.group(3)),
                ))
            if not conditions:
                raise ValueError(f"No valid conditions after 'where' in: '{part}'")

        rules.append(Rule(action=action, target=target, conditions=conditions))

    return rules
