"""Tests for the Telegram listener."""

import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from fliiq.runtime.telegram.listener import TelegramListener, _split_message, parse_allowed_chat_ids


@pytest.fixture
def listener(tmp_path):
    """Create a TelegramListener with a fake token."""
    mock_llm_config = MagicMock()
    return TelegramListener(
        bot_token="fake-token",
        llm_config=mock_llm_config,
        project_root=tmp_path,
    )


def test_split_message_short():
    assert _split_message("Hello") == ["Hello"]


def test_split_message_long():
    text = "a" * 5000
    chunks = _split_message(text)
    assert len(chunks) == 2
    assert len(chunks[0]) == 4096
    assert len(chunks[1]) == 904
    assert "".join(chunks) == text


def test_split_message_at_newline():
    text = "a" * 4000 + "\n" + "b" * 200
    chunks = _split_message(text)
    assert len(chunks) == 2
    assert chunks[0] == "a" * 4000
    assert chunks[1] == "b" * 200


def test_parse_allowed_chat_ids_empty(monkeypatch):
    monkeypatch.delenv("TELEGRAM_ALLOWED_CHAT_IDS", raising=False)
    assert parse_allowed_chat_ids() is None


def test_parse_allowed_chat_ids_valid(monkeypatch):
    monkeypatch.setenv("TELEGRAM_ALLOWED_CHAT_IDS", "123, 456, 789")
    result = parse_allowed_chat_ids()
    assert result == {123, 456, 789}


def test_parse_allowed_chat_ids_invalid(monkeypatch):
    monkeypatch.setenv("TELEGRAM_ALLOWED_CHAT_IDS", "abc, 123")
    result = parse_allowed_chat_ids()
    assert result is None


async def test_poll_updates_empty(listener):
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"ok": True, "result": []}

    mock_client = AsyncMock()
    mock_client.get.return_value = mock_response

    updates = await listener._poll_updates(mock_client)
    assert updates == []
    assert listener._offset == 0


async def test_poll_updates_with_message(listener):
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {
        "ok": True,
        "result": [
            {
                "update_id": 100,
                "message": {
                    "chat": {"id": 42},
                    "text": "Hello Fliiq",
                },
            }
        ],
    }

    mock_client = AsyncMock()
    mock_client.get.return_value = mock_response

    updates = await listener._poll_updates(mock_client)
    assert len(updates) == 1
    assert updates[0]["message"]["text"] == "Hello Fliiq"
    assert listener._offset == 101


async def test_poll_updates_error(listener):
    mock_response = MagicMock()
    mock_response.status_code = 500
    mock_response.json.return_value = {}

    mock_client = AsyncMock()
    mock_client.get.return_value = mock_response

    updates = await listener._poll_updates(mock_client)
    assert updates == []


async def test_send_message_success(listener):
    mock_response = MagicMock()
    mock_response.status_code = 200

    with patch("httpx.AsyncClient") as mock_client_cls:
        mock_client = AsyncMock()
        mock_client.post.return_value = mock_response
        mock_client_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client_cls.return_value.__aexit__ = AsyncMock(return_value=False)
        await listener.send_message(42, "Hello")

    mock_client.post.assert_called_once()
    call_kwargs = mock_client.post.call_args
    assert call_kwargs[1]["json"]["chat_id"] == 42
    assert call_kwargs[1]["json"]["text"] == "Hello"


async def test_send_message_long_text(listener):
    long_text = "a" * 5000
    mock_response = MagicMock()
    mock_response.status_code = 200

    with patch("httpx.AsyncClient") as mock_client_cls:
        mock_client = AsyncMock()
        mock_client.post.return_value = mock_response
        mock_client_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client_cls.return_value.__aexit__ = AsyncMock(return_value=False)
        await listener.send_message(42, long_text)

    assert mock_client.post.call_count == 2


async def test_allowed_chat_ids_filter(tmp_path):
    mock_llm_config = MagicMock()
    listener = TelegramListener(
        bot_token="fake-token",
        llm_config=mock_llm_config,
        project_root=tmp_path,
        allowed_chat_ids={42},
    )

    # Mock poll returning a message from blocked chat_id
    mock_poll_response = MagicMock()
    mock_poll_response.status_code = 200
    mock_poll_response.json.return_value = {
        "ok": True,
        "result": [
            {
                "update_id": 100,
                "message": {
                    "chat": {"id": 999},  # Not in allowed list
                    "text": "Blocked user",
                },
            }
        ],
    }

    # Mock a second poll that returns empty (to break the loop)
    mock_empty_response = MagicMock()
    mock_empty_response.status_code = 200
    mock_empty_response.json.return_value = {"ok": True, "result": []}

    call_count = 0

    async def _mock_get(*args, **kwargs):
        nonlocal call_count
        call_count += 1
        if call_count == 1:
            return mock_poll_response
        # Stop the listener after first poll
        await listener.stop()
        return mock_empty_response

    with patch("httpx.AsyncClient") as mock_client_cls:
        mock_client = AsyncMock()
        mock_client.get.side_effect = _mock_get
        mock_client_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client_cls.return_value.__aexit__ = AsyncMock(return_value=False)

        # Patch _handle_message to track if it was called
        with patch.object(listener, "_handle_message", new_callable=AsyncMock) as mock_handle:
            await listener.run()
            mock_handle.assert_not_called()  # Blocked chat_id → not handled


async def test_offset_tracking(listener):
    # First poll returns update_id 100
    resp1 = MagicMock()
    resp1.status_code = 200
    resp1.json.return_value = {
        "ok": True,
        "result": [{"update_id": 100, "message": {"chat": {"id": 42}, "text": "Hi"}}],
    }

    mock_client = AsyncMock()
    mock_client.get.return_value = resp1

    await listener._poll_updates(mock_client)
    assert listener._offset == 101

    # Second poll should use offset=101
    resp2 = MagicMock()
    resp2.status_code = 200
    resp2.json.return_value = {
        "ok": True,
        "result": [{"update_id": 101, "message": {"chat": {"id": 42}, "text": "Again"}}],
    }
    mock_client.get.return_value = resp2

    await listener._poll_updates(mock_client)
    assert listener._offset == 102

    # Verify the second call used offset=101
    second_call = mock_client.get.call_args_list[1]
    assert second_call[1]["params"]["offset"] == 101
