"""Voice processing and intent handling."""

from __future__ import annotations

import threading
import time
import unicodedata
from collections import deque
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from typing import Any, Callable, Deque, Dict, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from .behavior import RobotBehavior
    from .state import SharedState
    from .node import OutputPort, VoiceControlLogger


# Default action vocabulary (used when no behavior vocabulary is provided)
DEFAULT_ACTION_VOCABULARY: Dict[str, List[str]] = {
    "reiniciar": ["reiniciar", "reinicia", "reset"],
    "subir": ["subir", "sube", "arriba"],
    "bajar": ["bajar", "baja", "abajo"],
    "soltar": ["soltar", "deja", "dejar"],
    "tomar": ["tomar", "toma", "agarra", "agarrar", "coger", "chupar", "succionar"],
    "ir": ["ir", "ve", "mover", "muevete", "acercar"],
}


def normalize(text: str) -> str:
    """Normalize text: lowercase, remove accents."""
    text = text.lower().strip()
    text = unicodedata.normalize("NFKD", text)
    text = "".join([c for c in text if not unicodedata.combining(c)])
    return text


def rule_parse(transcript: str, vocabulary: Optional[Dict[str, List[str]]] = None) -> Dict[str, str]:
    """Parse voice command using rule-based approach.

    Args:
        transcript: The voice input text
        vocabulary: Optional action vocabulary mapping action names to keywords.
                   If not provided, uses the default vocabulary.
    """
    text = normalize(transcript)
    vocab = vocabulary if vocabulary is not None else DEFAULT_ACTION_VOCABULARY

    # Find matching action using vocabulary
    action = "error"
    for action_name, keywords in vocab.items():
        if any(normalize(w) in text for w in keywords):
            action = action_name
            break

    color = "no especificado"
    if "rojo" in text:
        color = "rojo"
    elif "azul" in text:
        color = "azul"
    elif "amarillo" in text:
        color = "amarillo"
    elif "blanco" in text:
        color = "blanco"

    obj = "no especificado"
    if "estrella" in text:
        obj = "estrella"
    elif "cilindro" in text:
        obj = "cilindro"
    elif "cubo" in text:
        obj = "cubo"
    elif "caja" in text:
        obj = "caja"

    size = "no especificado"
    if "grande" in text:
        size = "grande"
    elif "pequeno" in text or "pequeño" in text or "chico" in text:
        size = "pequeno"

    if action == "error":
        return {"resultado": "error"}
    return {
        "resultado": "ok",
        "accion": action,
        "objeto": obj,
        "color": color,
        "tamano": size,
    }


@dataclass
class Intent:
    """Parsed voice command intent."""

    action: str
    obj: str = "no especificado"
    color: str = "no especificado"
    size: str = "no especificado"

    def to_dict(self) -> Dict[str, str]:
        """Convert to dictionary."""
        return {
            "action": self.action,
            "obj": self.obj,
            "color": self.color,
            "size": self.size,
        }

    @classmethod
    def from_parse_result(cls, result: Dict[str, str]) -> Optional["Intent"]:
        """Create intent from parse result. Returns None if parsing failed."""
        if result.get("resultado") != "ok":
            return None
        return cls(
            action=result.get("accion", "error"),
            obj=result.get("objeto", "no especificado"),
            color=result.get("color", "no especificado"),
            size=result.get("tamano", "no especificado"),
        )


class IntentQueue:
    """Thread-safe queue for voice intents."""

    def __init__(self) -> None:
        self._queue: Deque[Intent] = deque()
        self._lock = threading.Lock()

    def append(self, intent: Intent) -> None:
        """Add intent to queue."""
        with self._lock:
            self._queue.append(intent)

    def popleft(self) -> Optional[Intent]:
        """Remove and return the oldest intent. Returns None if empty."""
        with self._lock:
            if self._queue:
                return self._queue.popleft()
            return None

    def __len__(self) -> int:
        """Return queue length."""
        with self._lock:
            return len(self._queue)

    def __bool__(self) -> bool:
        """Return True if queue has items."""
        with self._lock:
            return bool(self._queue)

    def clear(self) -> None:
        """Clear all intents."""
        with self._lock:
            self._queue.clear()


class VoiceInputHandler:
    """Handles voice_in events from the Dora node."""

    def __init__(
        self,
        state: "SharedState",
        behavior: "RobotBehavior",
        intent_queue: IntentQueue,
        port: "OutputPort",
        logger: "VoiceControlLogger",
    ) -> None:
        self._state = state
        self._behavior = behavior
        self._intent_queue = intent_queue
        self._port = port
        self._logger = logger
        self._executor = ThreadPoolExecutor(max_workers=1)

    def handle(self, event: Dict[str, Any]) -> None:
        """Handle a voice_in event. LLM parsing runs in background thread."""
        raw = event["value"][0].as_py() if len(event["value"]) else ""
        if not raw:
            return

        # Run LLM parsing in background thread to not block event loop
        self._executor.submit(self._process_and_respond, raw)

    def _process_and_respond(self, transcript: str) -> None:
        """Process command in background thread and send response."""
        response = self._process_command(transcript)
        self._port.send_json(response)

    def _process_command(self, transcript: str) -> Dict[str, str]:
        """Process voice command and return response."""
        self._logger.log(f'Voice input received: "{transcript}"')
        llm_result = self._behavior.parse_command(transcript)
        self._logger.log(f"Parse result: {llm_result}")

        # Update debug state
        self._state.update_voice_input(transcript, llm_result, time.monotonic())

        if llm_result.get("resultado") != "ok":
            self._logger.log("Command not understood")
            return {"text": "No entendi el comando", "status": "error"}

        intent = Intent.from_parse_result(llm_result)
        if intent:
            self._logger.log(
                "Intent: action=%s, object=%s, color=%s, size=%s"
                % (intent.action, intent.obj, intent.color, intent.size)
            )
            self._intent_queue.append(intent)

            # Add to history
            self._state.add_to_history(
                {
                    "timestamp": time.time(),
                    "input": transcript,
                    "action": intent.action,
                    "object": intent.obj,
                    "color": intent.color,
                    "size": intent.size,
                }
            )

        return {"text": f"Ok, voy a {llm_result.get('accion')}", "status": "ok"}

    def create_command_callback(self) -> Callable[[str], Dict[str, str]]:
        """Create a callback for the web interface to send commands."""
        def callback(transcript: str) -> Dict[str, str]:
            return self._process_command(transcript)

        return callback
