from __future__ import annotations

import threading
import uuid
from collections import defaultdict, deque
from dataclasses import dataclass
from typing import Any, Mapping


_LOCK = threading.Lock()
_PENDING_TRANSCRIPTS: dict[str, deque["RegisteredTurn"]] = defaultdict(deque)
_STARTED_TURN_MESSAGE_IDS: dict[str, str] = {}
_MAX_PENDING_TRANSCRIPTS = 32


@dataclass(frozen=True, slots=True)
class RegisteredTurn:
    message_id: str
    transcription: str
    text: str


def _string_or_empty(value: Any) -> str:
    return str(value or "").strip()


def _mapping_value(payload: Mapping[str, Any], *keys: str) -> str:
    for key in keys:
        value = payload.get(key)
        if value not in (None, ""):
            return _string_or_empty(value)

    lowered = {str(key).lower(): value for key, value in payload.items()}
    for key in keys:
        value = lowered.get(str(key).lower())
        if value not in (None, ""):
            return _string_or_empty(value)

    return ""


def _source_value(source: Any, *keys: str) -> str:
    if isinstance(source, Mapping):
        return _mapping_value(source, *keys)

    for key in keys:
        value = getattr(source, key, None)
        if value not in (None, ""):
            return _string_or_empty(value)
    return ""


def _router_callid(source: Any) -> str:
    day = _source_value(source, "RouterCallKeyDay", "routerCallKeyDay", "router_call_key_day")
    key = _source_value(source, "RouterCallKey", "routerCallKey", "router_call_key")
    if day or key:
        return f"{day}{key}"
    return ""


def turn_message_key(source: Any) -> str:
    return (
        _source_value(source, "session_id", "sessionId")
        or _router_callid(source)
        or _source_value(source, "callid", "call_id")
        or _source_value(source, "callIdGed", "call_id_ged", "message_id_seed")
        or _source_value(source, "protocol_id", "protocolId", "protocolo", "protocolNumber", "protocol")
        or "turn"
    )


def reset_turn_message_sequence(source: Any, *, clear_pending: bool = False) -> None:
    if not clear_pending:
        return

    key = turn_message_key(source)
    with _LOCK:
        _PENDING_TRANSCRIPTS.pop(key, None)
        _STARTED_TURN_MESSAGE_IDS.pop(key, None)


def next_turn_message_id(source: Any) -> str:
    del source
    return str(uuid.uuid4())


def register_started_turn_message_id(source: Any, message_id: str) -> None:
    normalized_message_id = _string_or_empty(message_id)
    if not normalized_message_id:
        return

    key = turn_message_key(source)
    with _LOCK:
        _STARTED_TURN_MESSAGE_IDS[key] = normalized_message_id


def peek_started_turn_message_id(source: Any) -> str:
    key = turn_message_key(source)
    with _LOCK:
        return _STARTED_TURN_MESSAGE_IDS.get(key, "")


def clear_started_turn_message_id(source: Any, message_id: str = "") -> None:
    key = turn_message_key(source)
    normalized_message_id = _string_or_empty(message_id)
    with _LOCK:
        if not normalized_message_id:
            _STARTED_TURN_MESSAGE_IDS.pop(key, None)
            return

        if _STARTED_TURN_MESSAGE_IDS.get(key) == normalized_message_id:
            _STARTED_TURN_MESSAGE_IDS.pop(key, None)


def register_transcribed_turn(
    source: Any,
    *,
    message_id: str,
    transcription: str = "",
    text: str = "",
) -> None:
    normalized_message_id = _string_or_empty(message_id)
    if not normalized_message_id:
        return

    key = turn_message_key(source)
    turn = RegisteredTurn(
        message_id=normalized_message_id,
        transcription=_string_or_empty(transcription),
        text=_string_or_empty(text),
    )
    with _LOCK:
        queue = _PENDING_TRANSCRIPTS[key]
        queue.append(turn)
        while len(queue) > _MAX_PENDING_TRANSCRIPTS:
            queue.popleft()


def consume_transcribed_turn(
    source: Any,
    *,
    transcription: str = "",
    text: str = "",
) -> RegisteredTurn | None:
    key = turn_message_key(source)
    normalized_transcription = _string_or_empty(transcription)
    normalized_text = _string_or_empty(text)

    with _LOCK:
        queue = _PENDING_TRANSCRIPTS.get(key)
        if not queue:
            return None

        for turn in tuple(queue):
            if normalized_transcription and turn.transcription == normalized_transcription:
                queue.remove(turn)
                return turn
            if normalized_text and turn.text == normalized_text:
                queue.remove(turn)
                return turn

        return queue.popleft()
