"""
카카오톡 파서 단위 테스트
테스터: 모리건 (개발3팀)
"""
import os

import pytest

from kakao_knowledge.kakao_parser import parse_kakao_chat
from kakao_knowledge.models import MessageType, UserMeta


# ---------------------------------------------------------------------------
# 헬퍼: 임시 카카오톡 파일 생성
# ---------------------------------------------------------------------------

HEADER = (
    "앞서가는 설계사, 스레드 보상스터디 님과 카카오톡 대화\n"
    "저장한 날짜 : 2026-04-08 13:11:28\n"
)


def make_chat_file(tmp_path, body: str) -> str:
    """헤더 + body 를 합쳐 임시 파일을 만들고 경로를 반환한다."""
    content = HEADER + "\n" + body
    p = tmp_path / "chat.txt"
    p.write_text(content, encoding="utf-8")
    return str(p)


def make_header_only_file(tmp_path) -> str:
    p = tmp_path / "header_only.txt"
    p.write_text(HEADER, encoding="utf-8")
    return str(p)


def make_empty_file(tmp_path) -> str:
    p = tmp_path / "empty.txt"
    p.write_text("", encoding="utf-8")
    return str(p)


# ---------------------------------------------------------------------------
# 1. 헤더 파싱 테스트
# ---------------------------------------------------------------------------

class TestHeaderParsing:
    def test_chat_name(self, tmp_path):
        result = parse_kakao_chat(make_chat_file(tmp_path, ""))
        assert result["header"]["chat_name"] == "앞서가는 설계사, 스레드 보상스터디 님과 카카오톡 대화"

    def test_save_date(self, tmp_path):
        result = parse_kakao_chat(make_chat_file(tmp_path, ""))
        assert result["header"]["save_date"] == "2026-04-08 13:11:28"


# ---------------------------------------------------------------------------
# 2. 일반 메시지 파싱
# ---------------------------------------------------------------------------

class TestBasicMessageParsing:
    def test_single_message_fields(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 광응고술 분쟁이 굉장히 많았습니다.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        messages = result["messages"]
        assert len(messages) == 1
        msg = messages[0]
        assert msg.date == "2025-12-03"
        assert msg.time == "17:46"
        assert msg.user == "이해철/프라임/부산"
        assert msg.type == MessageType.MESSAGE
        assert "광응고술" in msg.content

    def test_user_meta_parsed(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 광응고술 분쟁이 굉장히 많았습니다.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        meta = result["messages"][0].user_meta
        assert meta is not None
        assert meta.name == "이해철"
        assert meta.company == "프라임"
        assert meta.region == "부산"


# ---------------------------------------------------------------------------
# 3. 멀티라인 메시지 병합
# ---------------------------------------------------------------------------

class TestMultilineMessageMerge:
    def test_continuation_lines_merged(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 광응고술 분쟁이 굉장히 많았습니다.\n"
            "판결도 수술이다. 아니다. 엇갈렸죠.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        assert len(result["messages"]) == 1

    def test_continuation_content_has_newline(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 광응고술 분쟁이 굉장히 많았습니다.\n"
            "판결도 수술이다. 아니다. 엇갈렸죠.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        content = result["messages"][0].content
        assert "\n" in content
        assert "판결도 수술이다" in content

    def test_two_separate_messages_not_merged(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 첫 번째 메시지\n"
            "[이해철/프라임/부산] [오후 5:47] 두 번째 메시지\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        assert len(result["messages"]) == 2


# ---------------------------------------------------------------------------
# 4. 오전/오후 시간 변환
# ---------------------------------------------------------------------------

class TestTimeConversion:
    """오전/오후 시간을 24시간제 문자열로 올바르게 변환하는지 검증."""

    def _single_msg_time(self, tmp_path, time_str: str) -> str:
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            f"[테스터/테스트/서울] [{time_str}] 테스트 메시지\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        return result["messages"][0].time

    def test_afternoon(self, tmp_path):
        assert self._single_msg_time(tmp_path, "오후 5:46") == "17:46"

    def test_morning(self, tmp_path):
        assert self._single_msg_time(tmp_path, "오전 9:00") == "09:00"

    def test_noon(self, tmp_path):
        # 오후 12시 → 12:00
        assert self._single_msg_time(tmp_path, "오후 12:30") == "12:30"

    def test_midnight(self, tmp_path):
        # 오전 12시 → 00:xx
        assert self._single_msg_time(tmp_path, "오전 12:30") == "00:30"


# ---------------------------------------------------------------------------
# 5. 미디어 메시지 타입
# ---------------------------------------------------------------------------

class TestMediaMessageTypes:
    def _msg_type(self, tmp_path, content_line: str) -> MessageType:
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            f"[이해철/프라임/부산] [오후 5:47] {content_line}\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        return result["messages"][0].type

    def test_photo_type(self, tmp_path):
        assert self._msg_type(tmp_path, "사진") == MessageType.PHOTO

    def test_emoticon_type(self, tmp_path):
        assert self._msg_type(tmp_path, "이모티콘") == MessageType.EMOTICON

    def test_video_type(self, tmp_path):
        assert self._msg_type(tmp_path, "동영상") == MessageType.VIDEO

    def test_photo_multiple(self, tmp_path):
        # "사진 3장" 형태도 photo 로 분류되어야 한다
        assert self._msg_type(tmp_path, "사진 3장") == MessageType.PHOTO


# ---------------------------------------------------------------------------
# 6. 입퇴장/추방 메시지
# ---------------------------------------------------------------------------

class TestSystemJoinLeaveKick:
    def test_join_message_type(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "전종혁 / 인카 / 수원님이 들어왔습니다."
            "타인, 기관 등의 사칭에 유의해 주세요.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        join_msgs = [m for m in result["messages"] if m.type == MessageType.JOIN]
        assert len(join_msgs) >= 1

    def test_join_message_user(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "전종혁 / 인카 / 수원님이 들어왔습니다."
            "타인, 기관 등의 사칭에 유의해 주세요.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        join_msgs = [m for m in result["messages"] if m.type == MessageType.JOIN]
        assert join_msgs[0].user == "전종혁 / 인카 / 수원"

    def test_leave_message_type(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "김덕호 / KB / 대전님이 나갔습니다.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        leave_msgs = [m for m in result["messages"] if m.type == MessageType.LEAVE]
        assert len(leave_msgs) == 1

    def test_leave_message_user(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "김덕호 / KB / 대전님이 나갔습니다.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        leave_msgs = [m for m in result["messages"] if m.type == MessageType.LEAVE]
        assert leave_msgs[0].user == "김덕호 / KB / 대전"

    def test_kick_message_type(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "간식 먹는 프렌즈님을 내보냈습니다.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        kick_msgs = [m for m in result["messages"] if m.type == MessageType.KICK]
        assert len(kick_msgs) == 1

    def test_kick_message_user(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "간식 먹는 프렌즈님을 내보냈습니다.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        kick_msgs = [m for m in result["messages"] if m.type == MessageType.KICK]
        assert kick_msgs[0].user == "간식 먹는 프렌즈"


# ---------------------------------------------------------------------------
# 7. 오픈채팅봇 필터링
# ---------------------------------------------------------------------------

class TestBotFiltering:
    def test_bot_message_excluded_from_messages(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[오픈채팅봇] [오후 5:38] 📚 한 발 앞서가는 설계사, 환영합니다.\n"
            "━━━━━━━━━━━━━\n"
            "‼️ 대화명 변경 필수 ‼️\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        # bot 타입 메시지는 결과 messages 에 포함되지 않아야 한다
        for msg in result["messages"]:
            assert msg.type != MessageType.BOT, (
                f"bot 메시지가 messages 에 포함됨: {msg}"
            )

    def test_bot_message_not_counted_in_total_messages(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[오픈채팅봇] [오후 5:38] 환영합니다.\n"
            "[이해철/프라임/부산] [오후 5:46] 안녕하세요.\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        # 일반 메시지 1개만 포함되어야 한다
        assert len(result["messages"]) == 1
        assert result["messages"][0].user == "이해철/프라임/부산"


# ---------------------------------------------------------------------------
# 8. 닉네임 메타데이터 파싱
# ---------------------------------------------------------------------------

class TestNicknameMetaParsing:
    def _parse_meta(self, tmp_path, nickname: str) -> UserMeta:
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            f"[{nickname}] [오후 5:46] 테스트\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        return result["messages"][0].user_meta

    def test_three_parts_slash(self, tmp_path):
        meta = self._parse_meta(tmp_path, "이해철/프라임/부산")
        assert meta.name == "이해철"
        assert meta.company == "프라임"
        assert meta.region == "부산"

    def test_three_parts_slash_with_spaces(self, tmp_path):
        meta = self._parse_meta(tmp_path, "강나윤 / 인카 / 수도권")
        assert meta.name == "강나윤"
        assert meta.company == "인카"
        assert meta.region == "수도권"

    def test_two_parts_no_company(self, tmp_path):
        meta = self._parse_meta(tmp_path, "양희원/부산")
        assert meta.name == "양희원"
        assert meta.company is None
        assert meta.region == "부산"

    def test_no_slash_plain_name(self, tmp_path):
        meta = self._parse_meta(tmp_path, "하트를 든 라이언")
        assert meta.name == "하트를 든 라이언"
        assert meta.company is None
        assert meta.region is None

    def test_three_parts_mixed_spacing(self, tmp_path):
        # "정혜정/AIA/ 서울" — 앞뒤 공백 trim 처리 확인
        meta = self._parse_meta(tmp_path, "정혜정/AIA/ 서울")
        assert meta.name == "정혜정"
        assert meta.company == "AIA"
        assert meta.region == "서울"


# ---------------------------------------------------------------------------
# 9. 날짜 변경 추적
# ---------------------------------------------------------------------------

class TestDateTracking:
    def test_messages_get_correct_date_after_separator(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 12월 3일 메시지\n"
            "--------------- 2025년 12월 4일 목요일 ---------------\n"
            "[이해철/프라임/부산] [오전 9:00] 12월 4일 메시지\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        messages = result["messages"]
        assert len(messages) == 2
        assert messages[0].date == "2025-12-03"
        assert messages[1].date == "2025-12-04"

    def test_multiple_date_separators(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[A/B/C] [오후 1:00] 메시지1\n"
            "[A/B/C] [오후 2:00] 메시지2\n"
            "--------------- 2025년 12월 5일 금요일 ---------------\n"
            "[A/B/C] [오전 8:00] 메시지3\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        messages = result["messages"]
        assert messages[0].date == "2025-12-03"
        assert messages[1].date == "2025-12-03"
        assert messages[2].date == "2025-12-05"


# ---------------------------------------------------------------------------
# 10. 빈 파일 / 헤더만 있는 파일 처리
# ---------------------------------------------------------------------------

class TestEdgeCases:
    def test_empty_file_returns_empty_messages(self, tmp_path):
        result = parse_kakao_chat(make_empty_file(tmp_path))
        assert result["messages"] == []

    def test_empty_file_header_defaults(self, tmp_path):
        result = parse_kakao_chat(make_empty_file(tmp_path))
        # 헤더 키는 항상 존재해야 한다
        assert "chat_name" in result["header"]
        assert "save_date" in result["header"]

    def test_header_only_file_no_messages(self, tmp_path):
        result = parse_kakao_chat(make_header_only_file(tmp_path))
        assert result["messages"] == []

    def test_header_only_file_stats_zero(self, tmp_path):
        result = parse_kakao_chat(make_header_only_file(tmp_path))
        assert result["stats"]["total_messages"] == 0

    def test_stats_total_messages_matches_messages_list(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 메시지1\n"
            "[이해철/프라임/부산] [오후 5:47] 메시지2\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        assert result["stats"]["total_messages"] == len(result["messages"])

    def test_stats_total_lines_positive(self, tmp_path):
        body = (
            "--------------- 2025년 12월 3일 수요일 ---------------\n"
            "[이해철/프라임/부산] [오후 5:46] 메시지1\n"
        )
        result = parse_kakao_chat(make_chat_file(tmp_path, body))
        assert result["stats"]["total_lines"] > 0


# ---------------------------------------------------------------------------
# 11. 통합 테스트: 실제 샘플 파일
# ---------------------------------------------------------------------------

SAMPLE_PATH = "/home/jay/workspace/data/kakao-sample.txt"


@pytest.mark.integration
@pytest.mark.skipif(
    not os.path.exists(SAMPLE_PATH),
    reason=f"샘플 파일 없음: {SAMPLE_PATH}",
)
class TestIntegrationSampleFile:
    def test_message_count_reasonable(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        assert len(result["messages"]) >= 6000, (
            f"메시지 수가 6,000 미만: {len(result['messages'])}"
        )

    def test_no_bot_messages_in_result(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        bot_msgs = [m for m in result["messages"] if m.type == MessageType.BOT]
        assert len(bot_msgs) == 0, (
            f"bot 메시지 {len(bot_msgs)}개가 필터링되지 않고 포함됨"
        )

    def test_stats_total_lines(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        # 파서는 헤더 2줄을 readline()으로 별도 처리하므로
        # wc -l 값(32946) + 1 = 32947 로 집계된다
        assert result["stats"]["total_lines"] == 32947

    def test_stats_total_messages_consistent(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        assert result["stats"]["total_messages"] == len(result["messages"])

    def test_header_chat_name(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        assert result["header"]["chat_name"] == (
            "앞서가는 설계사, 스레드 보상스터디 님과 카카오톡 대화"
        )

    def test_header_save_date(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        assert result["header"]["save_date"] == "2026-04-08 13:11:28"

    def test_all_messages_have_date_and_time(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        # JOIN/LEAVE/KICK 메시지는 time이 ""일 수 있으므로 일반 메시지만 검증
        chat_types = {MessageType.MESSAGE, MessageType.PHOTO, MessageType.EMOTICON, MessageType.VIDEO}
        for msg in result["messages"]:
            assert msg.date, f"date 없음: {msg}"
            if msg.type in chat_types:
                assert msg.time, f"time 없음: {msg}"

    def test_all_chat_messages_have_user(self):
        result = parse_kakao_chat(SAMPLE_PATH)
        for msg in result["messages"]:
            if msg.type == MessageType.MESSAGE:
                assert msg.user, f"user 없음: {msg}"
