"""Unit tests for `app.services.tgbot.client.TelegramClient` retry/backoff logic.

NEVER calls real Telegram API — httpx.MockTransport only (consistent с
tests/services/test_dadata.py). `asyncio.sleep` is patched to a no-op so retry
tests run instantly regardless of configured backoff/retry_after durations.
"""

from __future__ import annotations

import os
from typing import Any
from unittest import mock

import httpx
import pytest

os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")

from app.services.tgbot.client import TelegramApiError, TelegramClient

_REAL_ASYNC_CLIENT = httpx.AsyncClient


def _install_transport(handler) -> None:
    transport = httpx.MockTransport(handler)

    def factory(*_: object, **__: object) -> httpx.AsyncClient:
        return _REAL_ASYNC_CLIENT(transport=transport)

    mock.patch("app.services.tgbot.client.httpx.AsyncClient", factory).start()


@pytest.fixture(autouse=True)
def _stop_patches_and_noop_sleep():
    sleep_patcher = mock.patch("app.services.tgbot.client.asyncio.sleep", return_value=None)
    sleep_patcher.start()
    yield
    mock.patch.stopall()


async def test_get_updates_happy_path_returns_list() -> None:
    def handler(request: httpx.Request) -> httpx.Response:
        assert request.url.path.endswith("/getUpdates")
        return httpx.Response(200, json={"ok": True, "result": [{"update_id": 1}]})

    _install_transport(handler)
    client = TelegramClient(token="fake-token")
    updates = await client.get_updates(offset=1)
    assert updates == [{"update_id": 1}]


async def test_never_logs_or_leaks_token_in_request_url_host() -> None:
    """Sanity: token lives only in the path, base host stays api.telegram.org."""
    captured: dict[str, str] = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["url"] = str(request.url)
        return httpx.Response(200, json={"ok": True, "result": {}})

    _install_transport(handler)
    client = TelegramClient(token="super-secret-token")
    await client.send_message(chat_id=1, text="hi")
    assert "bot" + "super-secret-token" in captured["url"]  # goes over the wire, not logged


async def test_copy_message_retries_on_429_then_succeeds() -> None:
    calls = {"n": 0}

    def handler(request: httpx.Request) -> httpx.Response:
        calls["n"] += 1
        if calls["n"] == 1:
            return httpx.Response(
                429,
                json={
                    "ok": False,
                    "error_code": 429,
                    "description": "Too Many Requests",
                    "parameters": {"retry_after": 3},
                },
            )
        return httpx.Response(200, json={"ok": True, "result": {"message_id": 5}})

    _install_transport(handler)
    client = TelegramClient(token="fake-token")
    result = await client.copy_message(chat_id=1, from_chat_id=2, message_id=3)

    assert result == {"message_id": 5}
    assert calls["n"] == 2


async def test_send_message_retries_on_5xx_then_succeeds() -> None:
    calls = {"n": 0}

    def handler(request: httpx.Request) -> httpx.Response:
        calls["n"] += 1
        if calls["n"] < 3:
            return httpx.Response(
                502, json={"ok": False, "error_code": 502, "description": "bad gw"}
            )
        return httpx.Response(200, json={"ok": True, "result": {"message_id": 9}})

    _install_transport(handler)
    client = TelegramClient(token="fake-token")
    result = await client.send_message(chat_id=1, text="retrying")

    assert result == {"message_id": 9}
    assert calls["n"] == 3


async def test_send_message_raises_immediately_on_non_retryable_4xx() -> None:
    calls = {"n": 0}

    def handler(request: httpx.Request) -> httpx.Response:
        calls["n"] += 1
        return httpx.Response(
            403, json={"ok": False, "error_code": 403, "description": "Forbidden: bot blocked"}
        )

    _install_transport(handler)
    client = TelegramClient(token="fake-token")

    with pytest.raises(TelegramApiError) as exc_info:
        await client.send_message(chat_id=1, text="hi")

    assert exc_info.value.error_code == 403
    assert calls["n"] == 1  # НЕ ретраится


async def test_copy_message_gives_up_after_max_retries_on_persistent_5xx() -> None:
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(500, json={"ok": False, "error_code": 500, "description": "boom"})

    _install_transport(handler)
    client = TelegramClient(token="fake-token")

    with pytest.raises(TelegramApiError) as exc_info:
        await client.copy_message(chat_id=1, from_chat_id=2, message_id=3)

    assert exc_info.value.error_code == 500


async def test_get_updates_returns_empty_list_on_malformed_result() -> None:
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(200, json={"ok": True, "result": "not-a-list"})

    _install_transport(handler)
    client = TelegramClient(token="fake-token")
    assert await client.get_updates(offset=1) == []


async def test_optional_thread_and_reply_params_omitted_when_falsy() -> None:
    captured: dict[str, Any] = {}

    def handler(request: httpx.Request) -> httpx.Response:
        import json as _json

        captured["body"] = _json.loads(request.content.decode("utf-8"))
        return httpx.Response(200, json={"ok": True, "result": {"message_id": 1}})

    _install_transport(handler)
    client = TelegramClient(token="fake-token")
    await client.copy_message(chat_id=1, from_chat_id=2, message_id=3, message_thread_id=0)

    assert "message_thread_id" not in captured["body"]
