"""Tests for in-process TelegramClient (Bot API, notify-only)."""

from __future__ import annotations

import json
from decimal import Decimal

import httpx
import pytest
from pytest_httpx import HTTPXMock

from cerbero_bite.clients.telegram import (
    TelegramClient,
    TelegramError,
    load_telegram_credentials,
)

SEND_URL = "https://api.telegram.org/botTOK/sendMessage"


def _client(**kw) -> TelegramClient:
    defaults = {"bot_token": "TOK", "chat_id": "42"}
    defaults.update(kw)
    return TelegramClient(**defaults)


def _request_body(httpx_mock: HTTPXMock) -> dict:
    request = httpx_mock.get_request()
    assert request is not None
    return json.loads(request.read())


# ---------------------------------------------------------------------------
# enabled / disabled
# ---------------------------------------------------------------------------


def test_enabled_when_both_token_and_chat_id_present() -> None:
    assert _client().enabled is True


def test_disabled_when_token_missing() -> None:
    c = TelegramClient(bot_token=None, chat_id="42")
    assert c.enabled is False


def test_disabled_when_chat_id_missing() -> None:
    c = TelegramClient(bot_token="TOK", chat_id=None)
    assert c.enabled is False


def test_disabled_when_token_blank() -> None:
    c = TelegramClient(bot_token="   ", chat_id="42")
    assert c.enabled is False


@pytest.mark.asyncio
async def test_disabled_notify_is_noop(httpx_mock: HTTPXMock) -> None:
    c = TelegramClient(bot_token=None, chat_id=None)
    await c.notify("hello")
    assert httpx_mock.get_requests() == []


# ---------------------------------------------------------------------------
# notify formatting
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_notify_sends_with_priority_and_tag(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True, "result": {}})
    await _client().notify("hello", priority="high", tag="entry")
    body = _request_body(httpx_mock)
    assert body["chat_id"] == "42"
    assert body["parse_mode"] == "HTML"
    assert body["text"] == "[HIGH][entry] hello"
    assert body["disable_web_page_preview"] is True


@pytest.mark.asyncio
async def test_notify_default_priority_normal(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify("plain")
    body = _request_body(httpx_mock)
    assert body["text"] == "[NORMAL] plain"


@pytest.mark.asyncio
async def test_notify_position_opened_formats_decimals(
    httpx_mock: HTTPXMock,
) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify_position_opened(
        instrument="ETH-15MAY26-2475-P",
        side="SELL",
        size=2,
        strategy="bull_put",
        greeks={"delta": Decimal("-0.04"), "vega": Decimal("0.20")},
        expected_pnl_usd=Decimal("45.00"),
    )
    text = _request_body(httpx_mock)["text"]
    assert "POSITION OPENED" in text
    assert "ETH-15MAY26-2475-P" in text
    assert "SELL" in text and "size: 2" in text and "bull_put" in text
    assert "delta=-0.0400" in text and "vega=+0.2000" in text
    assert "$+45.00" in text


@pytest.mark.asyncio
async def test_notify_position_opened_without_greeks(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify_position_opened(
        instrument="BTC-PERPETUAL", side="BUY", size=1, strategy="hedge"
    )
    text = _request_body(httpx_mock)["text"]
    assert "greeks" not in text
    assert "expected pnl" not in text


@pytest.mark.asyncio
async def test_notify_position_closed(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify_position_closed(
        instrument="ETH-15MAY26-2475-P_2350-P",
        realized_pnl_usd=Decimal("32.50"),
        reason="CLOSE_PROFIT",
    )
    text = _request_body(httpx_mock)["text"]
    assert "POSITION CLOSED" in text
    assert "ETH-15MAY26-2475-P_2350-P" in text
    assert "$+32.50" in text
    assert "CLOSE_PROFIT" in text


@pytest.mark.asyncio
async def test_notify_position_closed_negative_pnl(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify_position_closed(
        instrument="X", realized_pnl_usd=Decimal("-12.5"), reason="STOP"
    )
    text = _request_body(httpx_mock)["text"]
    assert "$-12.50" in text


@pytest.mark.asyncio
async def test_notify_alert(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify_alert(
        source="kill_switch", message="armed manually", priority="critical"
    )
    text = _request_body(httpx_mock)["text"]
    assert "ALERT [CRITICAL]" in text
    assert "kill_switch" in text and "armed manually" in text


@pytest.mark.asyncio
async def test_notify_system_error(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify_system_error(
        message="deribit feed anomaly", component="clients.deribit"
    )
    text = _request_body(httpx_mock)["text"]
    assert "SYSTEM ERROR [CRITICAL]" in text
    assert "deribit feed anomaly" in text
    assert "clients.deribit" in text


@pytest.mark.asyncio
async def test_notify_system_error_without_component(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    await _client().notify_system_error(message="boom")
    text = _request_body(httpx_mock)["text"]
    assert "component" not in text


# ---------------------------------------------------------------------------
# error paths
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_http_non_200_raises(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, status_code=500, text="upstream")
    with pytest.raises(TelegramError, match="HTTP 500"):
        await _client().notify("x")


@pytest.mark.asyncio
async def test_api_ok_false_raises(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(
        url=SEND_URL, json={"ok": False, "description": "chat not found"}
    )
    with pytest.raises(TelegramError, match="chat not found"):
        await _client().notify("x")


# ---------------------------------------------------------------------------
# shared httpx client
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_uses_shared_http_client(httpx_mock: HTTPXMock) -> None:
    httpx_mock.add_response(url=SEND_URL, json={"ok": True})
    shared = httpx.AsyncClient()
    try:
        c = _client(http_client=shared)
        await c.notify("x")
    finally:
        await shared.aclose()
    assert len(httpx_mock.get_requests()) == 1


# ---------------------------------------------------------------------------
# env-var loader
# ---------------------------------------------------------------------------


def test_load_credentials_returns_none_when_unset() -> None:
    assert load_telegram_credentials(env={}) == (None, None)


def test_load_credentials_strips_whitespace() -> None:
    env = {
        "CERBERO_BITE_TELEGRAM_BOT_TOKEN": "  abc  ",
        "CERBERO_BITE_TELEGRAM_CHAT_ID": "  -100  ",
    }
    assert load_telegram_credentials(env=env) == ("abc", "-100")


def test_load_credentials_treats_empty_as_none() -> None:
    env = {
        "CERBERO_BITE_TELEGRAM_BOT_TOKEN": "",
        "CERBERO_BITE_TELEGRAM_CHAT_ID": "   ",
    }
    assert load_telegram_credentials(env=env) == (None, None)
