"""Check operator engine: evaluate a single Check against resource attributes."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict

if TYPE_CHECKING:
    from complyform.core.profiles.models import Check


class CheckResult(BaseModel):
    """Result of evaluating a single check."""

    model_config = ConfigDict(strict=True, frozen=True)

    passed: bool
    actual_value: Any
    message: str


def get_nested_attribute(attributes: dict[str, Any], path: str) -> Any:
    """Resolve dot-separated path with numeric list indices.

    "settings.0.ip_configuration.0.ssl_mode" traverses dicts and lists.
    Returns _MISSING sentinel if path doesn't resolve.
    """
    current: Any = attributes
    for segment in path.split("."):
        if current is None:
            return _MISSING
        if isinstance(current, dict):
            if segment not in current:
                return _MISSING
            current = current[segment]
        elif isinstance(current, list):
            try:
                idx = int(segment)
            except ValueError:
                return _MISSING
            if idx < 0 or idx >= len(current):
                return _MISSING
            current = current[idx]
        else:
            return _MISSING
    return current


_MISSING = object()


class CheckEvaluator:
    """Evaluate checks against resource attributes."""

    def evaluate(self, check: Check, attributes: dict[str, Any]) -> CheckResult:
        """Evaluate a check. See module docstring for operator semantics."""
        value = get_nested_attribute(attributes, check.attribute)
        missing = value is _MISSING

        op = check.operator
        expected = check.expected
        fail_msg = check.failure_message

        if op == "equals":
            if missing:
                return CheckResult(
                    passed=False,
                    actual_value=None,
                    message=fail_msg or f"Attribute '{check.attribute}' missing",
                )
            passed = value == expected
            return CheckResult(
                passed=passed,
                actual_value=value,
                message=""
                if passed
                else (fail_msg or f"Expected {expected!r}, got {value!r}"),
            )

        if op == "not_equals":
            if missing:
                return CheckResult(passed=True, actual_value=None, message="")
            passed = value != expected
            return CheckResult(
                passed=passed,
                actual_value=value,
                message=""
                if passed
                else (fail_msg or f"Value must not be {expected!r}"),
            )

        if op == "not_empty":
            if missing:
                return CheckResult(
                    passed=False,
                    actual_value=None,
                    message=fail_msg or f"Attribute '{check.attribute}' missing",
                )
            passed = value is not None and value != "" and value != []
            return CheckResult(
                passed=passed,
                actual_value=value,
                message=""
                if passed
                else (fail_msg or f"Attribute '{check.attribute}' is empty"),
            )

        if op == "exists":
            passed = not missing
            return CheckResult(
                passed=passed,
                actual_value=None if missing else value,
                message=""
                if passed
                else (fail_msg or f"Attribute '{check.attribute}' does not exist"),
            )

        if op == "in_list":
            if missing:
                return CheckResult(
                    passed=False,
                    actual_value=None,
                    message=fail_msg or f"Attribute '{check.attribute}' missing",
                )
            passed = value in (expected or [])
            return CheckResult(
                passed=passed,
                actual_value=value,
                message=""
                if passed
                else (fail_msg or f"{value!r} not in {expected!r}"),
            )

        if op == "gte":
            if missing:
                return CheckResult(
                    passed=False,
                    actual_value=None,
                    message=fail_msg or f"Attribute '{check.attribute}' missing",
                )
            try:
                passed = float(value) >= float(expected)
            except TypeError, ValueError:
                return CheckResult(
                    passed=False,
                    actual_value=value,
                    message=fail_msg or f"Non-numeric value for gte: {value!r}",
                )
            return CheckResult(
                passed=passed,
                actual_value=value,
                message="" if passed else (fail_msg or f"{value} < {expected}"),
            )

        if op == "lte":
            if missing:
                return CheckResult(
                    passed=False,
                    actual_value=None,
                    message=fail_msg or f"Attribute '{check.attribute}' missing",
                )
            try:
                passed = float(value) <= float(expected)
            except TypeError, ValueError:
                return CheckResult(
                    passed=False,
                    actual_value=value,
                    message=fail_msg or f"Non-numeric value for lte: {value!r}",
                )
            return CheckResult(
                passed=passed,
                actual_value=value,
                message="" if passed else (fail_msg or f"{value} > {expected}"),
            )

        if op == "regex_match":
            if missing:
                return CheckResult(
                    passed=False,
                    actual_value=None,
                    message=fail_msg or f"Attribute '{check.attribute}' missing",
                )
            try:
                passed = bool(re.search(str(expected), str(value)))
            except re.error as exc:
                return CheckResult(
                    passed=False,
                    actual_value=value,
                    message=fail_msg or f"Invalid regex '{expected}': {exc}",
                )
            return CheckResult(
                passed=passed,
                actual_value=value,
                message=""
                if passed
                else (fail_msg or f"'{value}' does not match /{expected}/"),
            )

        return CheckResult(
            passed=False,
            actual_value=None,
            message=f"Unknown operator: {op}",
        )
