"""Local rule evaluation engine. No network calls."""

from __future__ import annotations

import re
from typing import Any

from .types import Guard, Operator, Properties, PropertyValue, Rule


def evaluate_guard(guard: Guard, properties: Properties) -> bool:
    """Evaluate `guard` against `properties` using Liteguard's rule semantics.

    Rules are checked in order and the first enabled matching rule decides the
    result. When no rule matches, the guard's configured default value is
    returned.
    """
    for rule in guard.rules:
        if not rule.enabled:
            continue
        if _matches_rule(rule, properties):
            return rule.result
    return guard.default_value


def _matches_rule(rule: Rule, properties: Properties) -> bool:
    raw = properties.get(rule.property_name)
    if raw is None:
        return False
    if not rule.values:
        return False

    op = rule.operator

    if op == Operator.EQUALS:
        return _values_equal(raw, rule.values[0] if rule.values else None)
    if op == Operator.NOT_EQUALS:
        return not _values_equal(raw, rule.values[0] if rule.values else None)
    if op == Operator.IN:
        return any(_values_equal(raw, v) for v in rule.values)
    if op == Operator.NOT_IN:
        return all(not _values_equal(raw, v) for v in rule.values)
    if op == Operator.REGEX:
        pattern = str(rule.values[0]) if rule.values else ""
        try:
            return bool(re.search(pattern, str(raw)))
        except re.error:
            return False
    if op == Operator.GT:
        comparison = _compare_ordered(raw, rule.values[0] if rule.values else None)
        return comparison is not None and comparison > 0
    if op == Operator.GTE:
        comparison = _compare_ordered(raw, rule.values[0] if rule.values else None)
        return comparison is not None and comparison >= 0
    if op == Operator.LT:
        comparison = _compare_ordered(raw, rule.values[0] if rule.values else None)
        return comparison is not None and comparison < 0
    if op == Operator.LTE:
        comparison = _compare_ordered(raw, rule.values[0] if rule.values else None)
        return comparison is not None and comparison <= 0
    return False


def _to_number(v: Any) -> float | None:
    if isinstance(v, bool):
        return None
    if isinstance(v, (int, float)):
        return float(v)
    return None


def _values_equal(a: PropertyValue, b: PropertyValue | None) -> bool:
    if b is None:
        return False
    if isinstance(a, bool) or isinstance(b, bool):
        return isinstance(a, bool) and isinstance(b, bool) and a is b
    if isinstance(a, str) or isinstance(b, str):
        return isinstance(a, str) and isinstance(b, str) and a == b
    na, nb = _to_number(a), _to_number(b)
    return na is not None and nb is not None and na == nb


def _compare_ordered(a: PropertyValue, b: PropertyValue | None) -> int | None:
    if b is None:
        return None
    if isinstance(a, str) or isinstance(b, str):
        if not isinstance(a, str) or not isinstance(b, str):
            return None
        if a < b:
            return -1
        if a > b:
            return 1
        return 0
    na, nb = _to_number(a), _to_number(b)
    if na is None or nb is None:
        return None
    if na < nb:
        return -1
    if na > nb:
        return 1
    return 0
