from unittest.mock import patch
from pathlib import Path

import pytest

from armorer.messaging.models import MessagingEvent
from armorer.messaging.bridge import _extract_core_message
from armorer.messaging.router import create_messaging_gateway
from armorer.messaging.signal import SignalGateway
from armorer.messaging.telegram import TelegramGateway


def _telegram_update() -> dict:
    return {
        "update_id": 100001,
        "message": {
            "message_id": 42,
            "date": 1739646000,
            "text": "hello from telegram",
            "chat": {"id": 777123, "type": "private"},
            "from": {"id": 9911, "username": "alice", "first_name": "Alice", "last_name": "Walker"},
        },
    }


def test_create_gateway_telegram():
    gw = create_messaging_gateway("telegram", bot_token="test-token")
    assert isinstance(gw, TelegramGateway)


def test_create_gateway_signal():
    gw = create_messaging_gateway("signal")
    assert isinstance(gw, SignalGateway)

def test_signal_send_not_implemented():
    gw = create_messaging_gateway("signal", endpoint="http://127.0.0.1:8085", account="+15551234567")
    with patch.object(gw, "_request", return_value={"timestamp": 1}) as mock_req:
        out = gw.send_message("+15550001111", "hello")
    assert out["ok"] is True
    mock_req.assert_called_once()


def test_signal_normalize_event():
    gw = SignalGateway(endpoint="http://127.0.0.1:8085", account="+15551234567")
    event = gw.normalize_event(
        {
            "envelope": {
                "source": "+15550001111",
                "sourceNumber": "+15550001111",
                "sourceName": "Alice",
                "timestamp": 1710000000000,
                "dataMessage": {"message": "hello", "timestamp": 1710000000000},
            }
        }
    )
    assert event is not None
    assert event.provider == "signal"
    assert event.channel_id == "+15550001111"
    assert event.text == "hello"


def test_create_gateway_invalid_provider():
    with pytest.raises(ValueError):
        create_messaging_gateway("whatsapp")


def test_telegram_normalize_event():
    gw = TelegramGateway(bot_token="test-token")
    event = gw.normalize_event(_telegram_update())

    assert isinstance(event, MessagingEvent)
    assert event.provider == "telegram"
    assert event.event_id == "100001"
    assert event.message_id == "42"
    assert event.channel_id == "777123"
    assert event.user_id == "9911"
    assert event.text == "hello from telegram"
    assert event.timestamp == 1739646000


def test_telegram_receive_webhook_aliases_normalize():
    gw = TelegramGateway(bot_token="test-token")
    event = gw.receive_webhook(_telegram_update())
    assert event is not None
    assert event.event_id == "100001"


def test_telegram_map_user():
    gw = TelegramGateway(bot_token="test-token")
    event = gw.normalize_event(_telegram_update())
    assert event is not None

    mapped = gw.map_user(event)
    assert mapped.external_user_id == "9911"
    assert mapped.external_channel_id == "777123"
    assert mapped.username == "alice"
    assert mapped.display_name == "Alice Walker"


def test_telegram_send_message():
    gw = TelegramGateway(bot_token="test-token")
    with patch.object(
        gw,
        "_api_post",
        return_value={"ok": True, "result": {"message_id": 88, "chat": {"id": 777123}}},
    ) as mock_post:
        out = gw.send_message("777123", "test outbound", reply_to="42")

    assert out["ok"] is True
    assert out["provider"] == "telegram"
    assert out["message_id"] == "88"
    mock_post.assert_called_once_with(
        "sendMessage",
        {"chat_id": "777123", "text": "test outbound", "reply_to_message_id": 42},
    )


def test_telegram_get_me():
    gw = TelegramGateway(bot_token="test-token")
    with patch.object(
        gw,
        "_api_post",
        return_value={"ok": True, "result": {"id": 101, "is_bot": True, "username": "armorer_bot"}},
    ) as mock_post:
        me = gw.get_me()

    assert me["username"] == "armorer_bot"
    mock_post.assert_called_once_with("getMe", {})


def test_telegram_get_updates():
    gw = TelegramGateway(bot_token="test-token")
    with patch.object(
        gw,
        "_api_post",
        return_value={
            "ok": True,
            "result": [{"update_id": 1001, "message": {"message_id": 10, "text": "hi", "chat": {"id": 1}, "from": {"id": 2}}},
            ],
        },
    ) as mock_post:
        updates = gw.get_updates(offset=1000, timeout=20, allowed_updates=["message"])

    assert isinstance(updates, list)
    assert updates[0]["update_id"] == 1001
    mock_post.assert_called_once_with(
        "getUpdates",
        {"timeout": 20, "offset": 1000, "allowed_updates": ["message"]},
        timeout=25,
    )


def test_telegram_send_chat_action():
    gw = TelegramGateway(bot_token="test-token")
    with patch.object(gw, "_api_post", return_value={"ok": True, "result": True}) as mock_post:
        out = gw.send_chat_action("777123", action="typing")

    assert out["ok"] is True
    assert out["provider"] == "telegram"
    mock_post.assert_called_once_with("sendChatAction", {"chat_id": "777123", "action": "typing"})


def test_telegram_answer_callback_query():
    gw = TelegramGateway(bot_token="test-token")
    with patch.object(gw, "_api_post", return_value={"ok": True, "result": True}) as mock_post:
        out = gw.answer_callback_query("cb-id", text="ok")
    assert out["ok"] is True
    mock_post.assert_called_once_with(
        "answerCallbackQuery",
        {"callback_query_id": "cb-id", "show_alert": False, "text": "ok"},
    )


def test_telegram_send_document(tmp_path: Path):
    gw = TelegramGateway(bot_token="test-token")
    doc = tmp_path / "report.pdf"
    doc.write_bytes(b"%PDF-1.4 test")
    with patch.object(
        gw,
        "_api_post_multipart",
        return_value={"ok": True, "result": {"message_id": 99}},
    ) as mock_post:
        out = gw.send_document("777123", str(doc), caption="daily")

    assert out["ok"] is True
    assert out["provider"] == "telegram"
    assert out["message_id"] == "99"
    mock_post.assert_called_once()


def test_extract_core_message_empty_json_message_uses_fallback():
    out = _extract_core_message('{"message": ""}')
    assert out == "I couldn't generate a response. Please try again."


def test_extract_core_message_prefers_error_field():
    out = _extract_core_message('{"message": "", "error": "Model backend unavailable"}')
    assert out == "Model backend unavailable"
