"""CommunicatorAgent — Layer 3 agent: NL intent parsing and channel-to-swarm routing.

Parses natural language instructions arriving from messaging channels
(WhatsApp, Telegram, Discord, Slack), extracts intent, and routes each
message to the correct swarm agent via SharedState.
"""

import logging
from typing import Any, Optional

from castor.command_interpreter import get_command_interpreter

from .base import BaseAgent
from .shared_state import SharedState

logger = logging.getLogger("OpenCastor.Agents.Communicator")

# Keyword → target agent routing table (first match wins, ordered by specificity)
_INTENT_ROUTING: list[tuple] = [
    # Manipulation
    ("grasp", "manipulator"),
    ("grab", "manipulator"),
    ("pick up", "manipulator"),
    ("pick", "manipulator"),
    ("place", "manipulator"),
    ("put down", "manipulator"),
    ("put", "manipulator"),
    ("push", "manipulator"),
    ("arm", "manipulator"),
    # Safety
    ("emergency", "guardian"),
    ("estop", "guardian"),
    ("e-stop", "guardian"),
    ("halt", "guardian"),
    ("stop", "guardian"),
    ("abort", "guardian"),
    # Navigation
    ("navigate", "navigator"),
    ("patrol", "navigator"),
    ("go to", "navigator"),
    ("move to", "navigator"),
    ("go", "navigator"),
    ("move", "navigator"),
    ("turn", "navigator"),
    ("forward", "navigator"),
    ("backward", "navigator"),
    ("left", "navigator"),
    ("right", "navigator"),
    # Observation
    ("detect", "observer"),
    ("scan", "observer"),
    ("watch", "observer"),
    ("observe", "observer"),
    ("look", "observer"),
    ("what do you see", "observer"),
    # Self-referential
    ("status", "communicator"),
    ("report", "communicator"),
    ("help", "communicator"),
    ("what can you", "communicator"),
]


class CommunicatorAgent(BaseAgent):
    """Human-robot interface agent for the Layer 3 swarm.

    Accepts raw text messages from any channel, determines intent, publishes
    a routed task to SharedState so the target agent can act on it.

    SharedState keys consumed:
        ``swarm.incoming_message`` — raw text string from a channel.

    SharedState keys published:
        ``swarm.routed_task.<agent>`` — dict with instruction, intent, from fields.
        ``swarm.incoming_message`` — cleared (set to None) after routing.
        ``swarm.communicator_response`` — formatted response string for channel reply.
    """

    name = "communicator"

    def __init__(
        self,
        config: Optional[dict[str, Any]] = None,
        shared_state: Optional[SharedState] = None,
    ):
        super().__init__(config)
        self._state = shared_state or SharedState()
        self._conversation_history: list[str] = []
        self._last_intent: Optional[str] = None
        self._last_routed_to: Optional[str] = None
        self._interpreter = get_command_interpreter()

    # ------------------------------------------------------------------
    # Public helpers (also useful in tests and channels)
    # ------------------------------------------------------------------

    def receive_message(self, text: str) -> None:
        """Inject an incoming message directly (bypasses SharedState read)."""
        self._state.set("swarm.incoming_message", text)

    def parse_intent(self, text: str) -> str:
        """Extract the primary intent keyword from a natural-language message.

        Returns the matching keyword or ``"unknown"`` if none match.
        """
        return self._interpreter.parse_intent(text).get("keyword", "unknown")

    def route_intent(self, intent: str, text: str) -> Optional[str]:
        """Publish a routed task to SharedState for the appropriate agent.

        Returns the target agent name, or ``None`` if intent is unknown.
        """
        target = next((agent for kw, agent in _INTENT_ROUTING if kw == intent), None)
        if target and target != "communicator":
            self._state.set(
                f"swarm.routed_task.{target}",
                {"instruction": text, "intent": intent, "from": self.name},
            )
            logger.info("Routed '%s' intent → %s", intent, target)
        return target

    def format_response(self, raw: str, intent: Optional[str] = None) -> str:
        """Format an agent result string for human-readable channel output."""
        raw = (raw or "").strip()
        if intent in ("stop", "emergency", "estop", "e-stop", "halt", "abort"):
            return f"Emergency stop acknowledged. {raw}".strip()
        if intent == "status":
            return f"Status: {raw}" if raw else "All systems normal."
        if intent == "help":
            return (
                "I can navigate, grab objects, scan the scene, and report status. "
                "Try: 'go forward', 'grab the cup', 'scan', or 'stop'."
            )
        return raw or "Done."

    # ------------------------------------------------------------------
    # BaseAgent interface
    # ------------------------------------------------------------------

    async def observe(self, sensor_data: dict[str, Any]) -> dict[str, Any]:
        """Pull latest incoming message from SharedState or sensor_data."""
        msg = sensor_data.get("incoming_message") or self._state.get("swarm.incoming_message")
        return {"message": msg}

    async def act(self, context: dict[str, Any]) -> dict[str, Any]:
        """Parse intent, route to target agent, and return routing result."""
        msg = context.get("message")
        if not msg:
            return {"action": "idle", "agent": self.name}

        self._conversation_history.append(msg)
        interpreted = self._interpreter.interpret(msg)
        intent = interpreted["intent"]["keyword"]
        self._last_intent = intent

        # Log explanation IDs mapped to policy decisions for auditability.
        safety = interpreted["safety"]
        self._state.set(
            f"swarm.policy_decision.{safety['explanation_id']}",
            self._interpreter.decision_records.get(safety["explanation_id"], {}),
        )
        self._state.set("swarm.last_structured_intent", interpreted)

        if not interpreted["execution_allowed"]:
            alt = safety.get("alternatives") or []
            alternatives = " | ".join(alt)
            response = (
                f"[{safety['explanation_id']}] Policy {safety['policy_id']} blocked this command: "
                f"{safety['rationale']} Safe alternatives: {alternatives}"
            ).strip()
            self._state.set("swarm.incoming_message", None)
            self._state.set("swarm.communicator_response", response)
            return {
                "action": "blocked",
                "intent": intent,
                "routed_to": None,
                "message": msg,
                "policy": safety,
            }

        target = self.route_intent(intent, msg)
        self._last_routed_to = target

        # Clear the processed message
        self._state.set("swarm.incoming_message", None)

        response = self.format_response("", intent)
        self._state.set("swarm.communicator_response", response)

        logger.debug("Communicator: '%s' → intent=%s target=%s", msg[:60], intent, target)
        return {
            "action": "route",
            "intent": intent,
            "routed_to": target,
            "message": msg,
            "structured_intent": interpreted,
        }
