"""Audited implementations for declarative Backplane cards."""

from __future__ import annotations

import ast
import json
import operator
import re
from dataclasses import dataclass, field
from typing import Any

from .contracts import CardPackage, GenerationRequest
from .errors import PolicyViolation


@dataclass
class CardContext:
    tenant_id: str
    request_id: str
    memory: dict[str, dict[str, str]]
    events: list[dict[str, Any]] = field(default_factory=list)
    short_circuit: str | None = None
    activation_specs: list[dict[str, Any]] = field(default_factory=list)

    def event(self, card_id: str, action: str, **detail: Any) -> None:
        self.events.append({"card": card_id, "action": action, **detail})


@dataclass(frozen=True)
class PreparedRequest:
    request: GenerationRequest
    prompt: str


class DeclarativeCard:
    def __init__(self, package: CardPackage):
        self.package = package
        self.manifest = package.manifest
        self.config = package.config
        if self.manifest.kind == "adapter":
            forbidden = {
                "adapter_path",
                "local_path",
                "model_path",
                "weights_path",
            } & set(self.config)
            if forbidden:
                raise ValueError(
                    "adapter packages may name an artifact hash or served model, "
                    "but local paths belong to the backend registry"
                )

    def before(self, prepared: PreparedRequest, context: CardContext) -> PreparedRequest:
        kind = self.manifest.kind
        prompt = prepared.prompt
        if kind == "policy":
            lowered = prompt.casefold()
            denied = [
                phrase
                for phrase in self.config.get("deny_phrases", [])
                if str(phrase).casefold() in lowered
            ]
            if denied:
                context.event(self.manifest.id, "blocked", rule="deny_phrases")
                raise PolicyViolation("request blocked by tenant policy")
            prefix = str(self.config.get("instruction", "")).strip()
            if prefix:
                prompt = f"[tenant policy]\n{prefix}\n[/tenant policy]\n\n{prompt}"
                context.event(self.manifest.id, "policy-attached")
        elif kind == "memory":
            selected: list[str] = []
            lowered = prompt.casefold()
            configured = self.config.get("entries", {})
            merged = {
                **{str(k): str(v) for k, v in configured.items()},
                **{key: item["value"] for key, item in context.memory.items()},
            }
            for key, value in merged.items():
                terms = [key, *self.config.get("triggers", {}).get(key, [])]
                if any(str(term).casefold() in lowered for term in terms):
                    selected.append(f"{key}: {value}")
            if selected:
                prompt = (
                    "[verified tenant memory]\n"
                    + "\n".join(selected[:8])
                    + "\n[/verified tenant memory]\n\n"
                    + prompt
                )
                context.event(
                    self.manifest.id,
                    "memory-read",
                    keys=[line.split(":", 1)[0] for line in selected[:8]],
                )
        elif kind == "tool":
            expression = _extract_expression(prompt)
            if expression:
                value = safe_calculate(expression)
                context.short_circuit = f"{expression} = {value}"
                context.event(self.manifest.id, "exact-compute", expression=expression)
        elif kind == "sensor":
            readings = self.config.get("readings", {})
            if readings:
                sensor_text = "\n".join(f"{key}: {value}" for key, value in readings.items())
                prompt = (
                    "[signed sensor frame]\n"
                    f"{sensor_text}\n"
                    "[/signed sensor frame]\n\n"
                    f"{prompt}"
                )
                context.event(
                    self.manifest.id, "sensor-read", channels=sorted(readings)
                )
        elif kind == "vocabulary":
            context.event(
                self.manifest.id,
                "vocabulary-mounted",
                terms=sorted(self.config.get("replacements", {})),
            )
        elif kind == "steering":
            spec = {"card_id": self.manifest.id, "kind": kind, **self.config}
            context.activation_specs.append(spec)
            context.event(self.manifest.id, "activation-card-armed")
        elif kind == "adapter":
            activation_mode = str(self.config.get("activation_mode", "always"))
            if activation_mode == "always":
                activate = True
            elif activation_mode == "metadata":
                metadata_key = str(
                    self.config.get("metadata_key", "activate_card")
                ).strip()
                if not metadata_key:
                    raise PolicyViolation("adapter metadata key cannot be blank")
                activate = (
                    prepared.request.metadata.get(metadata_key) == self.manifest.id
                )
            elif activation_mode == "typed-envelope":
                metadata_key = str(
                    self.config.get("metadata_key", "adapter_envelope")
                ).strip()
                payload = prepared.request.metadata.get(metadata_key)
                if payload is None:
                    activate = False
                else:
                    envelope = _parse_typed_adapter_envelope(
                        payload,
                        expected_operator_type=str(
                            self.config.get("operator_type", "")
                        ),
                        expected_term=str(self.config.get("term", "")),
                        expected_prompt=prepared.prompt,
                    )
                    activate = envelope["apply"]
            else:
                raise PolicyViolation("unsupported adapter activation mode")
            if activate:
                spec = {"card_id": self.manifest.id, "kind": kind, **self.config}
                context.activation_specs.append(spec)
                context.event(self.manifest.id, "adapter-armed")
            else:
                context.event(
                    self.manifest.id,
                    "adapter-skipped",
                    activation_mode=activation_mode,
                )
        return PreparedRequest(request=prepared.request, prompt=prompt)

    def after(self, text: str, context: CardContext) -> str:
        if self.manifest.kind == "policy":
            for term in self.config.get("redact", []):
                text = re.sub(re.escape(str(term)), "[redacted]", text, flags=re.I)
            required = str(self.config.get("required_suffix", "")).strip()
            if required and not text.rstrip().endswith(required):
                text = f"{text.rstrip()} {required}"
        elif self.manifest.kind == "vocabulary":
            for source, target in self.config.get("replacements", {}).items():
                text = re.sub(
                    rf"\b{re.escape(str(source))}\b",
                    str(target),
                    text,
                    flags=re.I,
                )
        return text


_BINARY_OPERATORS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.FloorDiv: operator.floordiv,
    ast.Mod: operator.mod,
    ast.Pow: operator.pow,
}
_UNARY_OPERATORS = {ast.UAdd: operator.pos, ast.USub: operator.neg}


def _extract_expression(prompt: str) -> str | None:
    match = re.search(
        r"(?:calculate|calc(?:ulate)?|compute)\s*[:=]?\s*([0-9\s()+\-*/%^]{1,120})",
        prompt,
        flags=re.I,
    )
    if not match:
        return None
    return match.group(1).strip().replace("^", "**")


def safe_calculate(expression: str) -> int | float:
    tree = ast.parse(expression, mode="eval")

    def evaluate(node: ast.AST) -> int | float:
        if isinstance(node, ast.Expression):
            return evaluate(node.body)
        if isinstance(node, ast.Constant) and type(node.value) in (int, float):
            return node.value
        if isinstance(node, ast.BinOp) and type(node.op) in _BINARY_OPERATORS:
            left, right = evaluate(node.left), evaluate(node.right)
            if isinstance(node.op, ast.Pow) and abs(right) > 12:
                raise PolicyViolation("exponent exceeds exact-compute budget")
            value = _BINARY_OPERATORS[type(node.op)](left, right)
            if abs(value) > 10**18:
                raise PolicyViolation("exact-compute result exceeds budget")
            return value
        if isinstance(node, ast.UnaryOp) and type(node.op) in _UNARY_OPERATORS:
            return _UNARY_OPERATORS[type(node.op)](evaluate(node.operand))
        raise PolicyViolation("unsupported exact-compute expression")

    return evaluate(tree)


def _parse_typed_adapter_envelope(
    payload: str,
    *,
    expected_operator_type: str,
    expected_term: str,
    expected_prompt: str,
) -> dict[str, Any]:
    """Parse the exact five-field caller-owned adapter control envelope."""

    if (
        not isinstance(payload, str)
        or not payload
        or len(payload.encode("utf-8")) > 32_768
    ):
        raise PolicyViolation("invalid adapter envelope size")
    if not expected_operator_type or not expected_term:
        raise PolicyViolation("typed adapter card lacks an operator contract")

    def object_pairs(pairs):
        result: dict[str, Any] = {}
        for key, value in pairs:
            if key in result:
                raise PolicyViolation(f"duplicate adapter envelope key: {key}")
            result[key] = value
        return result

    try:
        raw = json.loads(payload, object_pairs_hook=object_pairs)
    except json.JSONDecodeError as exc:
        raise PolicyViolation("invalid adapter envelope JSON") from exc
    expected_keys = {
        "schema_version",
        "operator_type",
        "term",
        "apply",
        "prompt",
    }
    if not isinstance(raw, dict) or set(raw) != expected_keys:
        raise PolicyViolation("adapter envelope keys mismatch")
    if type(raw["schema_version"]) is not int or raw["schema_version"] != 1:
        raise PolicyViolation("unsupported adapter envelope schema")
    if raw["operator_type"] != expected_operator_type:
        raise PolicyViolation("adapter envelope operator mismatch")
    if raw["term"] != expected_term:
        raise PolicyViolation("adapter envelope term mismatch")
    if type(raw["apply"]) is not bool:
        raise PolicyViolation("adapter envelope apply must be boolean")
    if (
        type(raw["prompt"]) is not str
        or raw["prompt"] != expected_prompt
        or not raw["prompt"].strip()
        or "\x00" in raw["prompt"]
        or len(raw["prompt"].encode("utf-8")) > 16_384
    ):
        raise PolicyViolation("adapter envelope prompt mismatch")
    return raw

