"""ABAC GuardrailEnv — attribute-based access control with rule evaluation."""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

from briefcase.guardrails.framework import (
    BaseGuardrailEnv,
    Effect,
    EvalRequest,
    EvalResult,
    Explanation,
    PolicySpace,
    SpaceBound,
)

_OPERATORS = {">=", "<=", ">", "<", "==", "!=", "in", "not_in"}


@dataclass
class ABACRule:
    """A single attribute-based access control rule."""
    attribute: str
    operator: str  # ">=", "<=", ">", "<", "==", "!=", "in", "not_in"
    value: Any
    effect: Effect = Effect.DENY  # what happens on rule violation


def _check_rule(rule: ABACRule, actual: Any) -> bool:
    """Return True if the rule is SATISFIED (no violation)."""
    op = rule.operator
    expected = rule.value
    if op == ">=":
        return actual >= expected
    elif op == "<=":
        return actual <= expected
    elif op == ">":
        return actual > expected
    elif op == "<":
        return actual < expected
    elif op == "==":
        return actual == expected
    elif op == "!=":
        return actual != expected
    elif op == "in":
        return actual in expected
    elif op == "not_in":
        return actual not in expected
    return False


class ABACGuardrailEnv(BaseGuardrailEnv):
    """Attribute-based access control guardrail.

    Evaluates a list of rules against request context. If ANY rule is
    violated, returns the rule's effect. If all pass, ALLOW.
    """

    def __init__(
        self,
        rules: Optional[List[ABACRule]] = None,
        name: str = "abac",
    ):
        rules = rules or []
        self._name = name
        self._rules = rules
        self._request_space = self._build_space(rules)

    @staticmethod
    def _build_space(rules: List[ABACRule]) -> PolicySpace:
        """Derive PolicySpace from rules."""
        context_schema: Dict[str, SpaceBound] = {}
        for rule in rules:
            if isinstance(rule.value, (int, float)) and rule.operator in (
                ">=", "<=", ">", "<",
            ):
                if rule.operator in (">=", ">"):
                    context_schema[rule.attribute] = SpaceBound(
                        low=float(rule.value), high=float("inf"),
                    )
                else:
                    context_schema[rule.attribute] = SpaceBound(
                        low=float("-inf"), high=float(rule.value),
                    )
            elif rule.attribute not in context_schema:
                context_schema[rule.attribute] = SpaceBound()
        return PolicySpace(context_schema=context_schema)

    def evaluate(self, request: EvalRequest) -> EvalResult:
        """Check all rules against request context."""
        start = time.monotonic()

        for rule in self._rules:
            actual = request.context.get(rule.attribute)
            if actual is None:
                elapsed = (time.monotonic() - start) * 1000.0
                return EvalResult(
                    effect=rule.effect,
                    guardrail_name=self.name,
                    reason=f"Missing attribute '{rule.attribute}'",
                    eval_time_ms=elapsed,
                    metadata={
                        "violated_rule": {
                            "attribute": rule.attribute,
                            "operator": rule.operator,
                            "expected": rule.value,
                            "actual": None,
                        },
                    },
                )

            if not _check_rule(rule, actual):
                elapsed = (time.monotonic() - start) * 1000.0
                return EvalResult(
                    effect=rule.effect,
                    guardrail_name=self.name,
                    reason=(
                        f"Rule violated: {rule.attribute} {rule.operator} "
                        f"{rule.value} (actual: {actual})"
                    ),
                    eval_time_ms=elapsed,
                    metadata={
                        "violated_rule": {
                            "attribute": rule.attribute,
                            "operator": rule.operator,
                            "expected": rule.value,
                            "actual": actual,
                        },
                    },
                )

        elapsed = (time.monotonic() - start) * 1000.0
        return EvalResult(
            effect=Effect.ALLOW,
            guardrail_name=self.name,
            reason="All ABAC rules passed",
            eval_time_ms=elapsed,
        )

    def explain(self, result: EvalResult) -> Explanation:
        """Produce ABAC-specific explanation with violated rule detail."""
        violated = result.metadata.get("violated_rule")
        extraction = None
        if violated:
            extraction = {
                "attribute": violated["attribute"],
                "value": violated["actual"],
                "quote": (
                    f"{violated['attribute']} is {violated['actual']}, "
                    f"expected {violated['operator']} {violated['expected']}"
                ),
            }
        return Explanation(
            decision_id=result.metadata.get("request_id"),
            effect=result.effect,
            guardrail_name=result.guardrail_name,
            extraction=extraction,
            abac_result=violated,
            eval_time_ms=result.eval_time_ms,
        )
