from __future__ import annotations

import hashlib
import json
from decimal import Decimal
from typing import Any, Mapping, Sequence, Tuple


def read_path(payload: Mapping[str, Any], path: str | None) -> Any:
    if not path:
        return None
    current: Any = payload
    for part in str(path).split("."):
        if not isinstance(current, Mapping):
            return None
        current = current.get(part)
    return current


def to_float(value: Any) -> float | None:
    if value is None:
        return None
    if isinstance(value, bool):
        return None
    if isinstance(value, (int, float)):
        return float(value)
    if isinstance(value, Decimal):
        return float(value)
    try:
        return float(str(value).strip())
    except (TypeError, ValueError):
        return None


def json_safe(value: Any) -> Any:
    if isinstance(value, Decimal):
        return float(value)
    if isinstance(value, Mapping):
        return {str(key): json_safe(item) for key, item in value.items()}
    if isinstance(value, list):
        return [json_safe(item) for item in value]
    if isinstance(value, tuple):
        return [json_safe(item) for item in value]
    return value


def build_trace_hash(payload: Mapping[str, Any]) -> str:
    """Return legacy trace hash shape (64-char hex digest)."""
    raw = json.dumps(json_safe(payload), ensure_ascii=False, sort_keys=True)
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()


def evaluate_policy_rules(
    rules: Sequence[Mapping[str, Any]],
    payload: Mapping[str, Any],
) -> Tuple[str, list[dict[str, Any]], list[dict[str, Any]]]:
    reasons: list[dict[str, Any]] = []
    events: list[dict[str, Any]] = []

    for rule in rules:
        rule_key = str(rule.get("rule_key") or "rule")
        definition = dict(rule.get("rule_definition") or {})
        rule_type = str(definition.get("type") or "").strip().lower()
        reason_code = str(
            rule.get("reason_code")
            or definition.get("reason_code")
            or f"{rule_key}_denied"
        )
        reason_message = str(
            rule.get("reason_message")
            or definition.get("message")
            or "Policy rule denied the request."
        )
        field_path = str(definition.get("field") or "").strip() or None

        event: dict[str, Any] = {
            "rule_key": rule_key,
            "rule_type": rule_type,
            "status": "passed",
            "message": "Rule passed",
            "reason_code": None,
            "field_path": field_path,
            "details": {},
        }

        if rule_type == "required_fields":
            fields = definition.get("fields") or []
            missing = [
                str(field)
                for field in fields
                if read_path(payload, str(field)) in (None, "", [])
            ]
            if missing:
                event["status"] = "failed"
                event["message"] = "Missing required fields"
                event["reason_code"] = reason_code
                event["details"] = {"missing_fields": missing}
                reasons.append(
                    {
                        "code": reason_code,
                        "message": reason_message,
                        "rule_key": rule_key,
                        "field_path": None,
                        "details": {"missing_fields": missing},
                    }
                )
        elif rule_type == "max_value":
            max_value = to_float(definition.get("max"))
            current = to_float(read_path(payload, field_path))
            if max_value is None or current is None:
                event["status"] = "failed"
                event["message"] = "Rule or payload is not numeric"
                event["reason_code"] = reason_code
                event["details"] = {"field": field_path, "max": max_value}
                reasons.append(
                    {
                        "code": reason_code,
                        "message": reason_message,
                        "rule_key": rule_key,
                        "field_path": field_path,
                        "details": {"field": field_path, "max": max_value},
                    }
                )
            elif current > max_value:
                event["status"] = "failed"
                event["message"] = "Value exceeds max"
                event["reason_code"] = reason_code
                event["details"] = {
                    "field": field_path,
                    "current": current,
                    "max": max_value,
                }
                reasons.append(
                    {
                        "code": reason_code,
                        "message": reason_message,
                        "rule_key": rule_key,
                        "field_path": field_path,
                        "details": {
                            "field": field_path,
                            "current": current,
                            "max": max_value,
                        },
                    }
                )
        elif rule_type == "allowed_values":
            allowed_values = list(definition.get("values") or [])
            current = read_path(payload, field_path)
            if current not in allowed_values:
                event["status"] = "failed"
                event["message"] = "Value is not allowed"
                event["reason_code"] = reason_code
                event["details"] = {
                    "field": field_path,
                    "current": current,
                    "allowed_values": allowed_values,
                }
                reasons.append(
                    {
                        "code": reason_code,
                        "message": reason_message,
                        "rule_key": rule_key,
                        "field_path": field_path,
                        "details": {
                            "field": field_path,
                            "current": current,
                            "allowed_values": allowed_values,
                        },
                    }
                )
        else:
            event["status"] = "failed"
            event["message"] = "Unsupported rule type"
            event["reason_code"] = "unsupported_rule_type"
            event["details"] = {"rule_type": rule_type}
            reasons.append(
                {
                    "code": "unsupported_rule_type",
                    "message": f"Unsupported rule type: {rule_type}",
                    "rule_key": rule_key,
                    "field_path": field_path,
                    "details": {"rule_type": rule_type},
                }
            )

        events.append(event)

    decision = "denied" if reasons else "approved"
    return decision, reasons, events
