import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
import pytest_asyncio

# ---------------------------------------------------------------------------
# 헬퍼: Telegram Update / Message 목 객체 생성
# ---------------------------------------------------------------------------


def _make_user(user_id: int, is_bot: bool = False, username: str = "testuser") -> MagicMock:
    user = MagicMock()
    user.id = user_id
    user.is_bot = is_bot
    user.username = username
    return user


def _make_bot_obj(bot_id: int = 999, username: str = "mybot") -> MagicMock:
    bot = MagicMock()
    bot.id = bot_id
    bot.username = username
    return bot


def _make_message(
    text: str,
    from_user: MagicMock,
    chat_type: str = "group",
    reply_to_message=None,
    entities=None,
) -> MagicMock:
    msg = MagicMock()
    msg.text = text
    msg.from_user = from_user
    msg.chat = MagicMock()
    msg.chat.type = chat_type
    msg.reply_to_message = reply_to_message
    msg.entities = entities or []
    msg.reply_text = AsyncMock()
    return msg


def _make_update(message: MagicMock) -> MagicMock:
    """telegram.Update를 모방하는 MagicMock을 반환한다"""
    update = MagicMock()
    update.message = message
    update.effective_message = message
    update.effective_user = message.from_user
    return update


def _make_context(bot_username: str = "mybot") -> MagicMock:
    ctx = MagicMock()
    ctx.bot = _make_bot_obj(username=bot_username)
    return ctx


# ---------------------------------------------------------------------------
# 테스트: 그룹챗 응답 조건 (bot_utils.should_respond)
# ---------------------------------------------------------------------------


class TestGroupChatResponseConditions:
    """그룹챗에서 봇이 응답해야 하는 / 하지 않아야 하는 조건 테스트"""

    def test_responds_to_mention(self):
        """그룹챗에서 @멘션 시 봇이 응답해야 한다"""
        from bot_utils import should_respond

        user = _make_user(user_id=111)

        # @mybot 멘션 엔티티 생성
        entity = MagicMock()
        entity.type = "mention"
        entity.offset = 0
        entity.length = len("@mybot")

        message = _make_message(
            text="@mybot 안녕하세요",
            from_user=user,
            chat_type="group",
            entities=[entity],
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is True

    def test_responds_to_reply_to_bot_message(self):
        """그룹챗에서 봇 메시지에 리플라이 시 봇이 응답해야 한다"""
        from bot_utils import should_respond

        user = _make_user(user_id=111)

        # 봇이 보낸 이전 메시지
        bot_user = _make_user(user_id=999, is_bot=True, username="mybot")
        original_bot_message = _make_message(text="저는 봇입니다.", from_user=bot_user, chat_type="group")

        message = _make_message(
            text="고마워!",
            from_user=user,
            chat_type="group",
            reply_to_message=original_bot_message,
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is True

    def test_responds_to_dm_from_owner(self):
        """오너 유저가 DM으로 메시지를 보내면 봇이 응답해야 한다"""
        from bot_utils import should_respond

        owner_id = 123456789
        owner_user = _make_user(user_id=owner_id)

        message = _make_message(
            text="봇아 일해라",
            from_user=owner_user,
            chat_type="private",
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=owner_id)
        assert result is True

    def test_does_not_respond_to_other_bot_messages(self):
        """다른 봇이 @멘션으로 보낸 메시지에는 응답해야 한다 (봇 간 토론 지원)"""
        from bot_utils import should_respond

        other_bot_user = _make_user(user_id=888, is_bot=True, username="otherbot")

        # 멘션 엔티티 포함 + 보낸 사람이 다른 봇 → 응답해야 함
        entity = MagicMock()
        entity.type = "mention"
        entity.offset = 0
        entity.length = len("@mybot")

        message = _make_message(
            text="@mybot 안녕",
            from_user=other_bot_user,
            chat_type="group",
            entities=[entity],
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is True

    def test_does_not_respond_to_self_bot_message(self):
        """자기 자신(같은 username) 봇이 보낸 메시지에는 응답하지 않아야 한다 (루프 방지)"""
        from bot_utils import should_respond

        self_bot_user = _make_user(user_id=999, is_bot=True, username="mybot")

        entity = MagicMock()
        entity.type = "mention"
        entity.offset = 0
        entity.length = len("@mybot")

        message = _make_message(
            text="@mybot 안녕",
            from_user=self_bot_user,
            chat_type="group",
            entities=[entity],
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is False

    def test_does_not_respond_to_regular_group_message(self):
        """멘션/리플라이 없는 일반 그룹 메시지에는 응답하지 않아야 한다"""
        from bot_utils import should_respond

        user = _make_user(user_id=111)

        message = _make_message(
            text="오늘 점심 뭐 먹지?",
            from_user=user,
            chat_type="group",
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is False

    def test_does_not_respond_to_dm_from_non_owner(self):
        """오너가 아닌 유저의 DM에는 응답하지 않아야 한다"""
        from bot_utils import should_respond

        owner_id = 123456789
        stranger = _make_user(user_id=999999999)

        message = _make_message(
            text="안녕 봇",
            from_user=stranger,
            chat_type="private",
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=owner_id)
        assert result is False

    def test_does_not_respond_to_reply_on_other_user_message(self):
        """봇이 아닌 다른 유저의 메시지에 리플라이한 경우 응답하지 않아야 한다"""
        from bot_utils import should_respond

        user_a = _make_user(user_id=111)
        user_b = _make_user(user_id=222)

        # user_a가 보낸 메시지에 user_b가 리플라이 (봇 메시지 아님)
        original_message = _make_message(text="원본 메시지", from_user=user_a, chat_type="group")
        message = _make_message(
            text="리플라이",
            from_user=user_b,
            chat_type="group",
            reply_to_message=original_message,
        )
        update = _make_update(message)

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is False

    def test_does_not_respond_when_message_is_none(self):
        """update.message가 None이면 응답하지 않아야 한다"""
        from bot_utils import should_respond

        update = MagicMock()
        update.message = None

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is False

    def test_does_not_respond_when_from_user_is_none(self):
        """message.from_user가 None이면 응답하지 않아야 한다"""
        from bot_utils import should_respond

        message = MagicMock()
        message.from_user = None
        message.chat = MagicMock()
        message.chat.type = "group"
        message.entities = []
        message.reply_to_message = None

        update = MagicMock()
        update.message = message

        result = should_respond(update, bot_username="mybot", owner_user_id=123456789)
        assert result is False


# ---------------------------------------------------------------------------
# 테스트: 메시지 분할 (bot_utils.split_message)
# ---------------------------------------------------------------------------


class TestMessageSplitting:
    """긴 메시지를 텔레그램 제한(4096자)에 맞게 분할하는 기능 테스트"""

    def test_short_message_not_split(self):
        """4096자 이하 메시지는 분할하지 않고 리스트 1개로 반환해야 한다"""
        from bot_utils import split_message

        short_text = "짧은 메시지입니다." * 10  # 약 90자
        result = split_message(short_text)

        assert isinstance(result, list)
        assert len(result) == 1
        assert result[0] == short_text

    def test_exact_limit_message_not_split(self):
        """정확히 4096자인 메시지는 분할하지 않아야 한다"""
        from bot_utils import split_message

        text_4096 = "a" * 4096
        result = split_message(text_4096)

        assert len(result) == 1
        assert result[0] == text_4096

    def test_long_message_split_into_multiple_parts(self):
        """4096자를 초과하는 메시지는 여러 파트로 분할되어야 한다"""
        from bot_utils import split_message

        long_text = "a" * 9000  # 4096 * 2보다 크다
        result = split_message(long_text)

        assert isinstance(result, list)
        assert len(result) >= 2

    def test_each_part_within_limit(self):
        """분할된 각 파트는 4096자를 초과하지 않아야 한다"""
        from bot_utils import split_message

        long_text = "b" * 15000
        result = split_message(long_text)

        for part in result:
            assert len(part) <= 4096

    def test_split_preserves_all_content(self):
        """분할 후 모든 파트를 합치면 원본 메시지와 동일해야 한다"""
        from bot_utils import split_message

        long_text = "c" * 10000
        result = split_message(long_text)

        assert "".join(result) == long_text

    def test_very_long_message_split_correctly(self):
        """매우 긴 메시지도 올바르게 분할되어야 한다"""
        from bot_utils import split_message

        very_long_text = "x" * 40960  # 4096 * 10
        result = split_message(very_long_text)

        assert len(result) >= 10
        for part in result:
            assert len(part) <= 4096
        assert "".join(result) == very_long_text

    def test_split_message_with_newlines(self):
        """줄바꿈이 포함된 긴 메시지도 올바르게 분할되어야 한다"""
        from bot_utils import split_message

        # 한 줄에 약 70자, 100줄 = 약 7000자
        long_text = "한글 텍스트 한 줄입니다. 여기에 내용이 있습니다.\n" * 100
        result = split_message(long_text)

        assert isinstance(result, list)
        for part in result:
            assert len(part) <= 4096

    def test_empty_message_returns_single_empty_string(self):
        """빈 문자열은 빈 문자열 1개를 포함한 리스트로 반환되어야 한다"""
        from bot_utils import split_message

        result = split_message("")

        assert isinstance(result, list)
        assert len(result) == 1
        assert result[0] == ""

    def test_custom_max_len_parameter(self):
        """max_len 파라미터를 지정하면 해당 값 기준으로 분할되어야 한다"""
        from bot_utils import split_message

        text = "a" * 300
        result = split_message(text, max_len=100)

        assert len(result) == 3
        for part in result:
            assert len(part) <= 100

    def test_single_line_exceeding_limit_is_force_split(self):
        """단일 줄이 max_len을 초과할 경우 강제로 분할되어야 한다"""
        from bot_utils import split_message

        # 줄바꿈 없이 8000자
        single_long_line = "z" * 8000
        result = split_message(single_long_line)

        assert len(result) >= 2
        for part in result:
            assert len(part) <= 4096
        assert "".join(result) == single_long_line


# ---------------------------------------------------------------------------
# 테스트: 응답 전송 (bot_utils.send_response)
# ---------------------------------------------------------------------------


class TestSendResponse:
    """send_response()가 메시지를 올바르게 분할하여 전송하는지 테스트"""

    @pytest.mark.asyncio
    async def test_send_long_response_sends_multiple_messages(self):
        """긴 응답을 전송할 때 여러 번 reply_text가 호출되어야 한다"""
        from bot_utils import send_response

        user = _make_user(user_id=111)
        message = _make_message(text="질문", from_user=user, chat_type="private")
        update = _make_update(message)
        context = _make_context()

        long_response = "응답 " * 2000  # 4096자 초과

        await send_response(update, context, long_response)

        # reply_text가 여러 번 호출되었는지 확인
        assert message.reply_text.call_count >= 2

    @pytest.mark.asyncio
    async def test_send_short_response_sends_single_message(self):
        """짧은 응답은 reply_text가 한 번만 호출되어야 한다"""
        from bot_utils import send_response

        user = _make_user(user_id=111)
        message = _make_message(text="질문", from_user=user, chat_type="private")
        update = _make_update(message)
        context = _make_context()

        short_response = "짧은 답변입니다."

        await send_response(update, context, short_response)

        assert message.reply_text.call_count == 1
        message.reply_text.assert_called_once_with("짧은 답변입니다.")

    @pytest.mark.asyncio
    async def test_send_response_calls_reply_text_with_correct_chunks(self):
        """send_response()는 분할된 각 청크를 순서대로 reply_text로 전송해야 한다"""
        from bot_utils import send_response

        user = _make_user(user_id=111)
        message = _make_message(text="질문", from_user=user, chat_type="private")
        update = _make_update(message)
        context = _make_context()

        # 정확히 두 청크가 될 텍스트: 4097자
        two_chunk_text = "a" * 4097

        await send_response(update, context, two_chunk_text)

        assert message.reply_text.call_count == 2
        # 첫 번째 청크는 4096자
        first_call_text = message.reply_text.call_args_list[0].args[0]
        assert len(first_call_text) <= 4096
        # 두 번째 청크는 1자
        second_call_text = message.reply_text.call_args_list[1].args[0]
        assert len(second_call_text) >= 1
