"""Safe, tri-state condition evaluator for applicability rules.

Conditions are plain data (no `eval`): a *leaf* is a 3-tuple
``(field, op, value)``; a *composite* is ``{"all": [...]}`` or
``{"any": [...]}``. Evaluation is tri-state — ``True`` / ``False`` /
``None`` (unknown) — so a missing product fact yields *uncertain*, never a
false negative.
"""

from __future__ import annotations

from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union

Leaf = Tuple[str, str, Any]
Condition = Union[Leaf, Dict[str, Any]]


def _attr(profile: Any, field: str) -> Any:
    value = getattr(profile, field, None)
    if isinstance(value, Enum):
        return value.value
    return value


def _eval_leaf(leaf: Leaf, profile: Any) -> Optional[bool]:
    field, op, expected = leaf
    actual = _attr(profile, field)

    if op == "not_none":
        return actual is not None
    if op == "is_none":
        return actual is None

    if op == "contains_any":
        # list-valued field (e.g. product_type); empty list = known-empty.
        items = actual or []
        hay = " ".join(str(x).lower() for x in items)
        return any(str(k).lower() in hay for k in expected)

    if actual is None:
        return None  # unknown fact -> unknown result

    if op == "eq":
        return bool(actual == expected)
    if op == "ne":
        return bool(actual != expected)
    if op == "truthy":
        return bool(actual)
    if op == "falsy":
        return not bool(actual)
    if op == "in":
        return bool(actual in expected)
    if op == "not_in":
        return bool(actual not in expected)
    if op == "date_after":
        return bool(actual > expected)
    raise ValueError("unknown predicate op: %r" % (op,))


def evaluate(condition: Optional[Condition], profile: Any) -> Optional[bool]:
    """Return True/False/None(unknown) for a condition tree."""
    if condition is None:
        return True
    if isinstance(condition, tuple):
        return _eval_leaf(condition, profile)

    if "all" in condition:
        results = [evaluate(c, profile) for c in condition["all"]]
        if any(r is False for r in results):
            return False
        if any(r is None for r in results):
            return None
        return True
    if "any" in condition:
        results = [evaluate(c, profile) for c in condition["any"]]
        if any(r is True for r in results):
            return True
        if any(r is None for r in results):
            return None
        return False
    raise ValueError("malformed condition: %r" % (condition,))


def true_leaves(condition: Optional[Condition], profile: Any) -> List[Leaf]:
    """Collect the leaf conditions that evaluated True (for trigger_facts)."""
    if condition is None:
        return []
    if isinstance(condition, tuple):
        return [condition] if _eval_leaf(condition, profile) is True else []
    members = condition.get("all") or condition.get("any") or []
    out: List[Leaf] = []
    for c in members:
        out.extend(true_leaves(c, profile))
    return out


def unknown_fields(fields: List[str], profile: Any) -> List[str]:
    """Subset of `fields` whose value on the profile is None (unknown)."""
    return [f for f in fields if _attr(profile, f) is None]
