from __future__ import annotations

import atexit
import copy
import json
import logging
import os
import queue
import re
import sys
import time
import uuid
from collections.abc import Iterable, Mapping
from contextvars import ContextVar
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from threading import Event, Lock, Thread
from typing import Any

from app.utils.structured_otlp import publish_structured_span
from app.utils.structured_pubsub import publish_structured_event

_LOGGER_CACHE: dict[str, logging.Logger] = {}
_LOGGER_LOCK = Lock()
_BASE_LOGGER_INITIALIZED = False
_LOG_SESSION_ID: ContextVar[str] = ContextVar("log_session_id", default="")


def set_log_session_id(session_id: str | None) -> None:
    """Bind the call session to the current async context and its child tasks."""
    _LOG_SESSION_ID.set(str(session_id or "").strip())


class _SessionConsoleFormatter(logging.Formatter):
    def __init__(self, fmt: str, *, fixed_session_id: str = "") -> None:
        super().__init__(fmt)
        self._fixed_session_id = str(fixed_session_id or "").strip()

    def format(self, record: logging.LogRecord) -> str:
        rendered = super().format(record)
        session_id = str(
            getattr(record, "session_id", "")
            or self._fixed_session_id
            or _LOG_SESSION_ID.get()
            or ""
        ).strip()
        if not session_id or re.search(
            r"(?i)(?:[\"']session_?id[\"']|\bsession_?id\b)\s*[:=]",
            rendered,
        ):
            return rendered
        return f"{rendered} | session_id={session_id}"


class _CallFileFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        match getattr(record, "structured_event", False):
            case True:
                return record.getMessage()
            case _:
                return super().format(record)


def _resolve_level(default: int) -> int:
    raw = (os.getenv("LOG_LEVEL", "") or "").strip().upper()
    match raw:
        case "":
            return default
        case _:
            value = logging.getLevelName(raw)
            match value:
                case int():
                    return value
                case _:
                    return default


EVENT_RECEBIMENTO_MSG = "recebimento msg"
EVENT_ENVIO_MSG = "envio msg"

_FINALIZATION_MATCHERS: tuple[tuple[str, tuple[str, ...]], ...] = (
    ("falha_stt", ("stt",)),
    ("falha_tts", ("tts",)),
    ("falha_agente", ("agent_backend", "falha agente")),
    ("falha_tia", ("agent_runtime", "bridge", "capacity")),
    ("silencio_longo", ("no_user_response", "silencio", "sil\u00eancio")),
    ("outro_assunto", ("outro_assunto", "other_subject")),
    ("nao_resolvido", ("nao_resolvido", "n\u00e3o resolvido", "unresolved")),
    ("resolvido", ("stage_done", "resolved", "resolvido", "done")),
    ("hang_up", ("disconnect", "hang")),
)

_ERROR_MATCHERS: tuple[tuple[str, tuple[str, ...]], ...] = (
    ("transferido", ("transferred", "transferido", "transferencia", "transfer\u00eancia")),
    ("silencio_longo", ("stop_silencio_longo", "no_user_response", "silencio", "sil\u00eancio")),
    ("capacidade_tia", ("capacity",)),
    ("falha_tia", ("agent_runtime", "livekit")),
    ("falha_stt", ("stt",)),
    ("falha_tts", ("tts",)),
    ("falha_comunicacao_agente", ("agent_backend", "remote_agent", "falha agente")),
    ("falha_comunicacao_gstreamer", ("bridge", "gstreamer", "gst")),
    ("capacidade_agente", ("agent_runtime",)),
)


@dataclass(frozen=True, slots=True)
class StructuredLogContext:
    callid: str
    session_id: str
    num_telefone: str
    cod_ani: str
    nome_agente: str
    message_id_seed: str = ""

    def as_metadata(self) -> dict[str, str]:
        return {
            "callid": self.callid,
            "session_id": self.session_id,
            "num_telefone": self.num_telefone,
            "cod_ani": self.cod_ani,
            "nome_agente": self.nome_agente,
            "message_id_seed": self.message_id_seed,
        }


def _pick_value(payload: Mapping[str, Any] | None, *keys: str) -> str:
    match payload:
        case Mapping():
            pass
        case _:
            return ""

    for key in keys:
        value = payload.get(key)
        if value not in (None, ""):
            return str(value).strip()

    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 str(value).strip()

    return ""


def normalize_agent_log_name(value: Any) -> str:
    raw = str(value or "").strip().lower()
    match raw:
        case "conta" | "contas":
            return "conta"
        case "ofert" | "oferta" | "ofertas":
            return "ofert"
        case "cobra" | "cobranca" | "cobran\u00e7a" | "cobrancas" | "cobran\u00e7as":
            return "cobra"
        case "":
            return "unknown"
        case _:
            return raw


def build_callid(
    *,
    router_call_key_day: str = "",
    router_call_key: str = "",
    call_id_ged: str = "",
    protocol: str = "",
    room: str = "",
) -> str:
    day = str(router_call_key_day or "").strip()
    key = str(router_call_key or "").strip()
    match (day, key):
        case ("", ""):
            return str(call_id_ged or protocol or room or "unknown").strip() or "unknown"
        case _:
            return f"{day}{key}"


def structured_context_from_start_data(
    data: Mapping[str, Any] | None,
    *,
    session_id: str = "",
) -> StructuredLogContext:
    router_day = _pick_value(data, "routerCallKeyDay", "RouterCallKeyDay", "router_call_key_day")
    router_key = _pick_value(data, "routerCallKey", "RouterCallKey", "router_call_key")
    call_id_ged = _pick_value(data, "callIdGed", "call_id_ged")
    callid = build_callid(
        router_call_key_day=router_day,
        router_call_key=router_key,
        call_id_ged=call_id_ged,
    )
    return StructuredLogContext(
        callid=callid,
        session_id=(
            str(session_id or "").strip()
            or _pick_value(data, "session_id", "sessionId")
        ),
        num_telefone=_pick_value(data, "gsm", "GSM", "msisdn", "NUM_TELEFONE", "phone"),
        cod_ani=_pick_value(data, "ani", "ANI", "cod_ani"),
        nome_agente=normalize_agent_log_name(_pick_value(data, "agent", "agente", "nome_agente", "Nome_agente")),
        message_id_seed=call_id_ged or f"MSG-{callid}",
    )


def structured_context_from_metadata(metadata: Mapping[str, Any] | None) -> StructuredLogContext:
    match metadata:
        case Mapping():
            structured_context = metadata.get("structured_log_context")
        case _:
            structured_context = None

    match structured_context:
        case Mapping():
            return StructuredLogContext(
                callid=_pick_value(structured_context, "callid") or "unknown",
                session_id=_pick_value(structured_context, "session_id", "sessionId"),
                num_telefone=_pick_value(structured_context, "num_telefone") or "unknown",
                cod_ani=_pick_value(structured_context, "cod_ani") or "unknown",
                nome_agente=normalize_agent_log_name(_pick_value(structured_context, "nome_agente", "Nome_agente")),
                message_id_seed=_pick_value(structured_context, "message_id_seed"),
            )
        case _:
            pass

    match metadata:
        case Mapping():
            session_data = metadata.get("session_data")
            remote_agent = metadata.get("remote_agent")
        case _:
            session_data = {}
            remote_agent = {}

    callid = build_callid(
        router_call_key_day=_pick_value(remote_agent, "RouterCallKeyDay", "routerCallKeyDay"),
        router_call_key=_pick_value(remote_agent, "RouterCallKey", "routerCallKey"),
        call_id_ged=_pick_value(metadata, "call_id_ged", "callIdGed"),
        protocol=_pick_value(metadata, "protocol"),
        room=_pick_value(metadata, "timeline_id"),
    )

    return StructuredLogContext(
        callid=callid,
        session_id=(
            _pick_value(metadata, "session_id", "sessionId")
            or _pick_value(remote_agent, "session_id", "sessionId")
        ),
        num_telefone=(
            _pick_value(session_data, "gsm", "GSM", "msisdn", "NUM_TELEFONE", "phone")
            or _pick_value(remote_agent, "GSM", "gsm", "NUM_TELEFONE")
            or "unknown"
        ),
        cod_ani=_pick_value(session_data, "ani", "ANI") or _pick_value(remote_agent, "ANI", "ani") or "unknown",
        nome_agente=normalize_agent_log_name(
            _pick_value(remote_agent, "agent", "agente") or _pick_value(session_data, "agent", "agente")
        ),
        message_id_seed=(
            _pick_value(metadata, "call_id_ged", "callIdGed")
            or _pick_value(remote_agent, "callIdGed", "call_id_ged")
            or f"MSG-{callid}"
        ),
    )


def format_event_timestamp(ns: int | None = None) -> str:
    match ns:
        case None:
            ts_ns = time.time_ns()
        case _:
            ts_ns = int(ns)
    seconds, remainder_ns = divmod(ts_ns, 1_000_000_000)
    dt = datetime.fromtimestamp(seconds, tz=timezone.utc)
    return f"{dt:%Y-%m-%dT%H:%M:%S}.{remainder_ns // 1_000_000:03d}Z"


def _format_optional_event_timestamp(ns: int | None) -> str | None:
    match ns:
        case None:
            return None
        case _:
            return format_event_timestamp(ns)


def event_latency_ms(start_ns: int | None, end_ns: int | None = None) -> int | None:
    match start_ns:
        case None:
            return None
        case _:
            started_ns = int(start_ns)

    match end_ns:
        case None:
            finished_ns = time.time_ns()
        case _:
            finished_ns = int(end_ns)
    return max(0, round((finished_ns - started_ns) / 1_000_000))


def _join_log_parts(*parts: str) -> str:
    return " ".join(str(item or "").strip().lower() for item in parts if str(item or "").strip())


def _match_first_token(text: str, matchers: tuple[tuple[str, tuple[str, ...]], ...]) -> str:
    for key, tokens in matchers:
        if any(token in text for token in tokens):
            return key
    return ""


def _structured_event_kind(tipo_evento: str = "") -> str:
    normalized = str(tipo_evento or "").strip().lower().replace("_", " ")
    match normalized:
        case "recebimento" | "recebimento msg":
            return "recebimento"
        case "envio" | "envio msg":
            return "envio"
        case _:
            return ""


def _event_kind_allows(kind: str, *allowed: str) -> bool:
    return not kind or kind in allowed


def finalization_from_status(
    *,
    status: str = "",
    reason: str = "",
    resource: str = "",
) -> str | None:
    normalized = _join_log_parts(status, reason, resource)
    match normalized:
        case "":
            return None
        case _:
            pass

    match _match_first_token(normalized, _FINALIZATION_MATCHERS):
        case "falha_stt":
            return "Falha STT"
        case "falha_tts":
            return "Falha TTS"
        case "falha_agente":
            return "Falha Agente"
        case "falha_tia":
            return "Falha TIA"
        case "silencio_longo":
            return "Sil\u00eancio Longo"
        case "outro_assunto":
            return "Outro Assunto"
        case "nao_resolvido":
            return "N\u00e3o Resolvido"
        case "resolvido":
            return "Resolvido"
        case "hang_up":
            return "hang up"
        case _:
            return reason or status or None


def error_message_from_resource(
    resource: str = "",
    status: str = "",
    reason: str = "",
    *,
    tipo_evento: str = "",
) -> str | None:
    normalized = _join_log_parts(resource, status, reason)
    match normalized:
        case "":
            return None
        case _:
            pass

    event_kind = _structured_event_kind(tipo_evento)
    match _match_first_token(normalized, _ERROR_MATCHERS):
        case "transferido" if _event_kind_allows(event_kind, "envio"):
            return "Transferido"
        case "silencio_longo" if _event_kind_allows(event_kind, "recebimento"):
            return "Silencio Longo"
        case "capacidade_tia":
            return "Capacidade TIA"
        case "falha_tia" if _event_kind_allows(event_kind, "recebimento", "envio"):
            return "Falha TIA"
        case "falha_stt" if _event_kind_allows(event_kind, "recebimento"):
            return "Falha STT"
        case "falha_tts" if _event_kind_allows(event_kind, "envio"):
            return "Falha TTS"
        case "falha_comunicacao_agente" if _event_kind_allows(event_kind, "envio"):
            return "Falha comunicacao"
        case "falha_comunicacao_gstreamer" if _event_kind_allows(event_kind, "recebimento"):
            return "Falha comunicacao"
        case "capacidade_agente":
            return "Capacidade Agente"
        case _:
            return None


def _event_field(value: Any) -> Any:
    return "" if value is None else value


def _event_flag(value: Any) -> int | None:
    if value is None:
        return None
    if isinstance(value, str):
        normalized = value.strip().lower()
        if not normalized:
            return None
        if normalized in {"1", "true", "yes", "on"}:
            return 1
        if normalized in {"0", "false", "no", "off"}:
            return 0
    return 1 if bool(value) else 0


def _env_flag(name: str, default: str = "1") -> bool:
    return (os.getenv(name, default) or default).strip().lower() in {"1", "true", "yes", "on"}


def _flow_value(value: Any) -> str:
    text = str(value)
    text = " ".join(text.split())
    return text.replace("|", "/")


def _flow_preview(value: Any, *, max_chars: int | None = None) -> str:
    if max_chars is None:
        try:
            max_chars = int(os.getenv("FLOW_LOG_PREVIEW_CHARS", "500"))
        except ValueError:
            max_chars = 500
    text = _flow_value(value)
    if len(text) <= max_chars:
        return text
    return f"{text[:max_chars]}..."


def log_flow_event(logger: Any, step: str, **fields: Any) -> None:
    if not _env_flag("FLOW_LOG_ENABLED", "1"):
        return

    safe_fields: dict[str, str] = {}
    for key, value in fields.items():
        if value is None:
            continue
        if isinstance(value, str) and not value.strip():
            continue
        if key in {"text", "transcript", "reply", "payload", "result", "output", "content"}:
            safe_fields[key] = _flow_preview(value)
        else:
            safe_fields[key] = _flow_value(value)

    parts = [f"step={_flow_value(step or 'unknown')}"]
    parts.extend(f"{key}={value}" for key, value in safe_fields.items())
    logger.info("FLOW | %s", " | ".join(parts))


def build_structured_event(
    context: StructuredLogContext,
    *,
    tipo_evento: str,
    message_id: str | None = None,
    inicio_ns: int | None = None,
    fim_ns: int | None = None,
    latencia_total_ms: int | None = None,
    latencia_tffb_ms: int | None = None,
    duracao_audio_ms: int | None = None,
    tts_max_gap_ms: int | None = None,
    tts_max_underrun_0ms: int | None = None,
    tts_underflow_count: int | None = None,
    tts_avg_underflow_ms: int | None = None,
    interrupcao: bool | int | str | None = None,
    erro_msg: str | None = None,
    erro_detalhe: str | None = None,
    http_cod_status: int | str | None = None,
    http_cod_desc: str | None = None,
    finalizacao: str | None = None,
) -> dict[str, Any]:
    return {
        "tipo_evento": str(tipo_evento or ""),
        "message_id": str(message_id or uuid.uuid4()),
        "dat_hora_inicio": format_event_timestamp(inicio_ns),
        "dat_hora_fim": _event_field(_format_optional_event_timestamp(fim_ns)),
        "callid": str(context.callid or ""),
        "session_id": str(context.session_id or ""),
        "num_telefone": str(context.num_telefone or ""),
        "cod_ani": str(context.cod_ani or ""),
        "latencia_total_STT_TTS": _event_field(latencia_total_ms),
        "latencia_TFFB_STT_TTS": _event_field(latencia_tffb_ms),
        "duracao_audio": _event_field(duracao_audio_ms),
        "tts_max_gap_ms": _event_field(tts_max_gap_ms),
        "tts_max_underrun_0ms": _event_field(tts_max_underrun_0ms),
        "tts_underflow_count": _event_field(tts_underflow_count),
        "tts_avg_underflow_ms": _event_field(tts_avg_underflow_ms),
        "interrupcao": _event_field(_event_flag(interrupcao)),
        "erro_msg": _event_field(erro_msg),
        "erro_detalhe": _event_field(erro_detalhe),
        "http_cod_status": _event_field(http_cod_status),
        "http_cod_desc": _event_field(http_cod_desc),
        "finalizacao": _event_field(finalizacao),
        "nome_agente": str(context.nome_agente or ""),
    }


def log_structured_event(
    logger: Any,
    context: StructuredLogContext | None,
    *,
    tipo_evento: str,
    message_id: str | None = None,
    inicio_ns: int | None = None,
    fim_ns: int | None = None,
    latencia_total_ms: int | None = None,
    latencia_tffb_ms: int | None = None,
    duracao_audio_ms: int | None = None,
    tts_max_gap_ms: int | None = None,
    tts_max_underrun_0ms: int | None = None,
    tts_underflow_count: int | None = None,
    tts_avg_underflow_ms: int | None = None,
    interrupcao: bool | int | str | None = None,
    erro_msg: str | None = None,
    erro_detalhe: str | None = None,
    http_cod_status: int | str | None = None,
    http_cod_desc: str | None = None,
    finalizacao: str | None = None,
) -> dict[str, Any] | None:
    match (os.getenv("STRUCTURED_EVENT_LOG_ENABLED", "1"), context):
        case ("1", StructuredLogContext()):
            pass
        case _:
            return None

    event = build_structured_event(
        context,
        tipo_evento=tipo_evento,
        message_id=message_id,
        inicio_ns=inicio_ns,
        fim_ns=fim_ns,
        latencia_total_ms=latencia_total_ms,
        latencia_tffb_ms=latencia_tffb_ms,
        duracao_audio_ms=duracao_audio_ms,
        tts_max_gap_ms=tts_max_gap_ms,
        tts_max_underrun_0ms=tts_max_underrun_0ms,
        tts_underflow_count=tts_underflow_count,
        tts_avg_underflow_ms=tts_avg_underflow_ms,
        interrupcao=interrupcao,
        erro_msg=erro_msg,
        erro_detalhe=erro_detalhe,
        http_cod_status=http_cod_status,
        http_cod_desc=http_cod_desc,
        finalizacao=finalizacao,
    )
    logger.info(json.dumps(event, ensure_ascii=False), extra={"structured_event": True})
    publish_structured_event(event)
    publish_structured_span(event)
    return event


def _sanitize_for_filename(value: str) -> str:
    value = (value or "").strip()
    match value:
        case "":
            return "unknown"
        case _:
            pass
    value = re.sub(r"\D+", "", value)
    return value or "unknown"


def _bounded_env_int(name: str, default: int, *, minimum: int, maximum: int) -> int:
    try:
        value = int(os.getenv(name, str(default)) or str(default))
    except (TypeError, ValueError):
        value = default
    return max(minimum, min(maximum, value))


class _LogWriterCommand:
    __slots__ = ("kind", "event")

    def __init__(self, kind: str) -> None:
        self.kind = kind
        self.event = Event()


class _LogWriter:
    """Executa handlers de arquivo numa thread dedicada e observável."""

    def __init__(
        self,
        max_queue: int,
        *,
        warning_interval_s: float = 60.0,
    ) -> None:
        self._queue: queue.Queue[
            tuple[logging.Handler, logging.LogRecord] | _LogWriterCommand
        ] = queue.Queue(maxsize=max(1, max_queue))
        self._thread: Thread | None = None
        self._lifecycle_lock = Lock()
        self._stats_lock = Lock()
        self._closed = False
        self._dropped = 0
        self._write_errors = 0
        self._last_drop_warning_at = 0.0
        self._last_error_warning_at = 0.0
        self._warning_interval_s = max(1.0, float(warning_interval_s))
        self._logger = logging.getLogger(f"{__name__}.writer")

    @property
    def dropped(self) -> int:
        with self._stats_lock:
            return self._dropped

    @property
    def write_errors(self) -> int:
        with self._stats_lock:
            return self._write_errors

    def start(self) -> bool:
        with self._lifecycle_lock:
            if self._closed:
                return False
            if self._thread is not None and self._thread.is_alive():
                return True
            self._thread = Thread(
                target=self._run,
                name="call-log-writer",
                daemon=True,
            )
            self._thread.start()
            return True

    def submit(self, target: logging.Handler, record: logging.LogRecord) -> bool:
        if not self.start():
            self._record_drop(reason="writer_closed")
            return False
        reason = ""
        with self._lifecycle_lock:
            if self._closed:
                reason = "writer_closed"
            else:
                try:
                    self._queue.put_nowait((target, record))
                    return True
                except queue.Full:
                    reason = "queue_full"
        self._record_drop(reason=reason)
        return False

    def flush(self, timeout: float | None = 2.0) -> bool:
        with self._lifecycle_lock:
            if self._closed:
                return self._queue.empty()
        if not self.start():
            return False
        command = _LogWriterCommand("flush")
        deadline = None if timeout is None else time.monotonic() + max(0.0, timeout)
        with self._lifecycle_lock:
            if self._closed:
                return self._queue.empty()
            if not self._enqueue_command(command, deadline):
                return False
        remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
        return command.event.wait(remaining)

    def shutdown(self, timeout: float | None = 2.0) -> bool:
        with self._lifecycle_lock:
            if self._closed:
                thread = self._thread
                return thread is None or not thread.is_alive()
            self._closed = True
            thread = self._thread

        if thread is None:
            return True

        command = _LogWriterCommand("shutdown")
        deadline = None if timeout is None else time.monotonic() + max(0.0, timeout)
        if not self._enqueue_command(command, deadline):
            return False
        remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
        if not command.event.wait(remaining):
            return False
        remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
        thread.join(remaining)
        return not thread.is_alive()

    def _enqueue_command(
        self,
        command: _LogWriterCommand,
        deadline: float | None,
    ) -> bool:
        try:
            if deadline is None:
                self._queue.put(command)
            else:
                self._queue.put(command, timeout=max(0.0, deadline - time.monotonic()))
            return True
        except queue.Full:
            return False

    def _run(self) -> None:
        while True:
            item = self._queue.get()
            if isinstance(item, _LogWriterCommand):
                item.event.set()
                if item.kind == "shutdown":
                    return
                continue

            target, record = item
            try:
                target.handle(record)
            except Exception as exc:
                self._record_write_error(target=target, exc=exc)

    def _record_drop(self, *, reason: str) -> None:
        now = time.monotonic()
        with self._stats_lock:
            self._dropped += 1
            dropped = self._dropped
            should_warn = now - self._last_drop_warning_at >= self._warning_interval_s
            if should_warn:
                self._last_drop_warning_at = now
        if should_warn:
            self._logger.warning(
                "CALL_LOG_ASYNC_DROP | reason=%s | dropped_total=%s | queue_size=%s",
                reason,
                dropped,
                self._queue.qsize(),
            )

    def _record_write_error(
        self,
        *,
        target: logging.Handler,
        exc: Exception,
    ) -> None:
        now = time.monotonic()
        with self._stats_lock:
            self._write_errors += 1
            errors = self._write_errors
            should_warn = now - self._last_error_warning_at >= self._warning_interval_s
            if should_warn:
                self._last_error_warning_at = now
        if should_warn:
            self._logger.warning(
                "CALL_LOG_ASYNC_WRITE_FAIL | handler=%s | error=%s: %s | errors_total=%s",
                type(target).__name__,
                type(exc).__name__,
                exc,
                errors,
            )


_LOG_WRITER = _LogWriter(
    _bounded_env_int(
        "CALL_LOG_QUEUE_MAX",
        20_000,
        minimum=1,
        maximum=1_000_000,
    ),
    warning_interval_s=_bounded_env_int(
        "ASYNC_IO_WARNING_INTERVAL_S",
        60,
        minimum=1,
        maximum=3_600,
    ),
)
atexit.register(_LOG_WRITER.shutdown)


class _AsyncFileHandler(logging.Handler):
    """Enfileira uma cópia estável do registro para escrita em background."""

    def __init__(self, target: logging.Handler) -> None:
        super().__init__(level=target.level)
        self._target = target
        _LOG_WRITER.start()

    def emit(self, record: logging.LogRecord) -> None:
        clone = copy.copy(record)
        try:
            clone.msg = clone.getMessage()
            clone.args = None
        except Exception:
            pass
        _LOG_WRITER.submit(self._target, clone)

    def flush(self) -> None:
        writer = globals().get("_LOG_WRITER")
        if writer is not None:
            writer.flush(timeout=2.0)

    def close(self) -> None:
        try:
            self.flush()
        finally:
            try:
                self._target.close()
            finally:
                super().close()


def _build_call_file_handler(file_path: Path, level: int) -> logging.FileHandler:
    handler = logging.FileHandler(file_path, encoding="utf-8")
    handler.setLevel(level)
    handler.setFormatter(
        _CallFileFormatter(
            "%(asctime)s | %(levelname)s | %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )
    )
    return handler


def setup_minimal_logging(*, level: int = logging.INFO) -> logging.Logger:
    """
    - Root: WARNING
    - Logger base: console simples
    - Não cria arquivo por processo
    - Arquivo apenas por chamada
    """
    global _BASE_LOGGER_INITIALIZED

    level = _resolve_level(level)
    logger_name = os.getenv("APP_LOGGER_NAME", "agent_internal_stt")

    match _BASE_LOGGER_INITIALIZED:
        case True:
            log = logging.getLogger(logger_name)
            log.setLevel(level)
            return log
        case _:
            pass

    root = logging.getLogger()
    root.setLevel(logging.WARNING)

    for h in list(root.handlers):
        root.removeHandler(h)
        try:
            h.close()
        except Exception:
            pass

    root_handler = logging.StreamHandler(sys.stdout)
    root_handler.setLevel(logging.WARNING)
    root_handler.setFormatter(
        _SessionConsoleFormatter("%(levelname)s %(name)s: %(message)s")
    )
    root.addHandler(root_handler)

    noisy: Iterable[str] = (
        "livekit",
        "livekit.agents",
        "livekit.plugins",
        "livekit.rtc",
        "aioice",
        "aiortc",
        "httpx",
        "httpcore",
        "asyncio",
        "stt_internal",
        "transformers",
        "uvicorn.access",
    )
    for name in noisy:
        logging.getLogger(name).setLevel(logging.WARNING)

    log = logging.getLogger(logger_name)
    log.setLevel(level)
    log.propagate = False

    for h in list(log.handlers):
        log.removeHandler(h)
        try:
            h.close()
        except Exception:
            pass

    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setLevel(level)
    console_handler.setFormatter(_SessionConsoleFormatter("%(message)s"))
    log.addHandler(console_handler)

    _BASE_LOGGER_INITIALIZED = True
    return log


def get_call_logger(
    *,
    phone_number: str | None = None,
    session_id: str | None = None,
    level: int = logging.INFO,
) -> logging.Logger:
    """
    Logger dedicado por ligação.
    Nome do arquivo: telefone_timestamp.txt
    Ex: 68981100139_20260306_142317.txt
    """
    log_dir = Path(os.getenv("LOG_DIR", "./logs"))
    logger_name = os.getenv("APP_LOGGER_NAME", "agent_internal_stt")
    log_to_file = os.getenv("LOG_TO_FILE", "1") == "1"
    level = _resolve_level(level)

    safe_phone = _sanitize_for_filename(phone_number or "")
    safe_session = _sanitize_for_filename(session_id or "")
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    cache_key = f"{logger_name}.call.{safe_phone}.{safe_session or timestamp}"

    with _LOGGER_LOCK:
        cached = _LOGGER_CACHE.get(cache_key)
        match cached:
            case logging.Logger():
                return cached
            case _:
                pass

        log = logging.getLogger(cache_key)
        log.setLevel(level)
        log.propagate = False

        for h in list(log.handlers):
            log.removeHandler(h)
            try:
                h.close()
            except Exception:
                pass

        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setLevel(level)
        console_handler.setFormatter(
            _SessionConsoleFormatter("%(message)s", fixed_session_id=safe_session)
        )
        log.addHandler(console_handler)

        match log_to_file:
            case True:
                log_dir.mkdir(parents=True, exist_ok=True)

                file_name = f"{safe_phone}_{timestamp}.txt"
                file_path = log_dir / file_name

                log.addHandler(_AsyncFileHandler(_build_call_file_handler(file_path, level)))
                log.info(
                    "CALL_FILE_LOG_ENABLED | phone=%s | session_id=%s | path=%s",
                    safe_phone,
                    safe_session or "-",
                    file_path,
                )
            case _:
                pass

        _LOGGER_CACHE[cache_key] = log
        return log
