"""
Comprehensive Unit Tests for FreeExile Social Engine:
Friend Types, Social Repository (SQLite), Social Service, and Guild Roster Service.
"""

from __future__ import annotations
import pytest
from server.social.social_types import (
    FriendDTO,
    FriendshipStatus,
    GuildMemberPresenceDTO,
    PresenceStatus,
    SocialActionResponse,
)
from server.social.social_repository import SocialRepository
from server.social.social_service import SocialService
from server.social.guild_roster_service import GuildRosterService
from server.trade.guild_stash_service import GuildMember, GuildRole


class FakeChannelManager:
    """Mock for ChannelManager to test blacklist synchronization."""

    def __init__(self) -> None:
        self.blacklist_calls: list[tuple[int, int, bool]] = []
        self._player_blacklists: dict[int | str, set[int | str]] = {}

    def set_blacklist_entry(self, player_id: int, blocked_player_id: int, is_blocked: bool) -> None:
        self.blacklist_calls.append((player_id, blocked_player_id, is_blocked))
        if player_id not in self._player_blacklists:
            self._player_blacklists[player_id] = set()
        if is_blocked:
            self._player_blacklists[player_id].add(blocked_player_id)
        else:
            self._player_blacklists[player_id].discard(blocked_player_id)


class TestSocialTypes:
    """Tests for Social DTOs, Enums, and properties."""

    def test_friendship_and_presence_enums(self) -> None:
        assert FriendshipStatus.NONE.value == "NONE"
        assert FriendshipStatus.ACCEPTED.value == "ACCEPTED"
        assert FriendshipStatus.BLOCKED.value == "BLOCKED"
        assert PresenceStatus.ONLINE.value == "ONLINE"
        assert PresenceStatus.OFFLINE.value == "OFFLINE"

    def test_friend_dto_fields_and_properties(self) -> None:
        dto = FriendDTO(
            player_id="p1",
            character_name="BladeMaster",
            level=50,
            character_class_or_online="SwordMaster",
            presence_status_or_zone_id=PresenceStatus.ONLINE,
            zone_id_or_zone_name="zone_tang_kiem_nhai",
            current_map_name_or_note="Tàng Kiếm Nhai",
            friend_note="Reliable tank",
        )
        assert dto.player_id == "p1"
        assert dto.character_name == "BladeMaster"
        assert dto.name == "BladeMaster"
        assert dto.level == 50
        assert dto.character_class == "SwordMaster"
        assert dto.is_online is True
        assert dto.zone_id == "zone_tang_kiem_nhai"
        assert dto.zone_name == "Tàng Kiếm Nhai"
        assert dto.note == "Reliable tank"

    def test_guild_member_presence_dto_dual_signatures(self) -> None:
        # Standard signature
        g1 = GuildMemberPresenceDTO(
            player_id="g1",
            character_name="Leader",
            role="Đầu Lĩnh",
            level=80,
            presence_status=PresenceStatus.ONLINE,
            zone_id="zone_tang_kiem_nhai",
            is_teleport_targetable=True,
        )
        assert g1.name == "Leader"
        assert g1.rank == "Đầu Lĩnh"
        assert g1.is_online is True
        assert g1.is_teleport_targetable is True

        # Alternate positional signature (player_id, name, level, rank, is_online, zone_id)
        g2 = GuildMemberPresenceDTO("g2", "Elder", 70, "Trưởng Lão", True, "zone_tang_kiem_nhai")
        assert g2.player_id == "g2"
        assert g2.name == "Elder"
        assert g2.level == 70
        assert g2.rank == "Trưởng Lão"
        assert g2.is_online is True
        assert g2.is_teleport_targetable is True

    def test_social_action_response(self) -> None:
        resp = SocialActionResponse(success=True, message="Success", action_code="OK")
        assert resp.success is True
        assert resp.message == "Success"
        assert resp.action_code == "OK"


class TestSocialRepository:
    """Tests for SQLite persistence layer in SocialRepository."""

    @pytest.fixture
    def repo(self) -> SocialRepository:
        return SocialRepository(db_path=":memory:")

    def test_friend_request_lifecycle(self, repo: SocialRepository) -> None:
        assert repo.add_friend_request("alice", "bob") is True
        assert repo.add_friend_request("alice", "bob") is False  # Duplicate rejected
        assert repo.add_friend_request("alice", "alice") is False  # Self-request rejected

        assert repo.get_pending_requests("bob") == ["alice"]
        assert repo.get_sent_requests("alice") == ["bob"]

        # Accept
        assert repo.accept_friend_request("bob", "alice") is True
        assert repo.is_friend("alice", "bob") is True
        assert repo.is_friend("bob", "alice") is True

        # Note setting
        assert repo.set_friend_note("alice", "bob", "Best DPS") is True
        assert repo.get_friend_note("alice", "bob") == "Best DPS"
        assert repo.set_friend_note("alice", "stranger", "Invalid") is False

        # Friends query
        friends = repo.get_friends("alice")
        assert len(friends) == 1
        assert friends[0]["friend_id"] == "bob"
        assert friends[0]["friend_note"] == "Best DPS"

        # Removal
        assert repo.remove_friend("alice", "bob") is True
        assert repo.is_friend("alice", "bob") is False
        assert repo.is_friend("bob", "alice") is False

    def test_decline_request(self, repo: SocialRepository) -> None:
        repo.add_friend_request("u1", "u2")
        assert repo.decline_friend_request("u2", "u1") is True
        assert repo.is_friend("u1", "u2") is False
        assert repo.get_pending_requests("u2") == []

    def test_block_and_mutual_block(self, repo: SocialRepository) -> None:
        repo.add_friend_request("a", "b")
        repo.accept_friend_request("b", "a")
        assert repo.is_friend("a", "b") is True

        # A blocks B
        assert repo.block_player("a", "b") is True
        assert repo.is_blocked("a", "b") is True
        assert repo.is_blocked("b", "a") is True  # Mutual block verification
        assert repo.is_friend("a", "b") is False
        assert repo.is_friend("b", "a") is False

        # Blocked list
        assert repo.get_blocked_list("a") == ["b"]
        assert repo.get_blocked_list("b") == []

        # Request while blocked must fail
        assert repo.add_friend_request("b", "a") is False
        assert repo.add_friend_request("a", "b") is False

        # Unblock
        assert repo.unblock_player("a", "b") is True
        assert repo.is_blocked("a", "b") is False
        assert repo.is_blocked("b", "a") is False


class TestSocialService:
    """Tests for SocialService business logic and ChannelManager synchronization."""

    @pytest.fixture
    def service_env(self) -> tuple[SocialService, FakeChannelManager]:
        repo = SocialRepository(db_path=":memory:")
        chat_mgr = FakeChannelManager()
        svc = SocialService(repository=repo, channel_manager=chat_mgr)
        return svc, chat_mgr

    def test_service_friend_lifecycle(self, service_env: tuple[SocialService, FakeChannelManager]) -> None:
        svc, _ = service_env
        assert svc.send_friend_request("p1", "p2") is True
        assert "p1" in svc.friend_requests["p2"]
        assert svc.accept_friend_request("p2", "p1") is True
        assert "p1" not in svc.friend_requests.get("p2", set())
        assert svc.is_friend("p1", "p2") is True

        assert svc.set_friend_note("p1", "p2", "Dungeon Runner") is True
        svc.update_presence("p2", "PlayerTwo", 60, True, "zone_wild", "Wilderness")

        presence = svc.get_friend_presence("p1")
        assert len(presence) == 1
        assert presence[0].player_id == "p2"
        assert presence[0].name == "PlayerTwo"
        assert presence[0].level == 60
        assert presence[0].is_online is True
        assert presence[0].note == "Dungeon Runner"

        assert svc.remove_friend("p1", "p2") is True
        assert svc.is_friend("p1", "p2") is False

    def test_block_syncs_with_channel_manager(self, service_env: tuple[SocialService, FakeChannelManager]) -> None:
        svc, chat_mgr = service_env
        svc.block_player("101", "202")
        assert (101, 202, True) in chat_mgr.blacklist_calls
        assert svc.is_blocked("101", "202") is True
        assert svc.is_blocked("202", "101") is True

        svc.unblock_player("101", "202")
        assert (101, 202, False) in chat_mgr.blacklist_calls
        assert svc.is_blocked("101", "202") is False

    def test_blocked_presence_is_hidden(self, service_env: tuple[SocialService, FakeChannelManager]) -> None:
        svc, _ = service_env
        svc.send_friend_request("alice", "bob")
        svc.accept_friend_request("bob", "alice")
        svc.update_presence("bob", "Bob", 45, True, "zone_tang_kiem_nhai", "Tàng Kiếm Nhai")

        assert len(svc.get_friend_presence("alice")) == 1
        svc.block_player("alice", "bob")
        assert len(svc.get_friend_presence("alice")) == 0
        assert len(svc.get_friend_presence("bob")) == 0

    def test_canonical_update_player_presence(self, service_env: tuple[SocialService, FakeChannelManager]) -> None:
        svc, _ = service_env
        svc.send_friend_request("alice", "bob")
        svc.accept_friend_request("bob", "alice")

        svc.update_player_presence("bob", 75, "zone_sanctuary", PresenceStatus.IN_COMBAT, "BobTheSlayer", "Berserker")
        presence = svc.get_friend_presence("alice")
        assert len(presence) == 1
        assert presence[0].character_class == "Berserker"
        assert presence[0].presence_status == PresenceStatus.IN_COMBAT
        assert presence[0].level == 75


class TestGuildRosterService:
    """Tests for GuildRosterService presence joining and teleport targetability."""

    def test_roster_with_presence_and_targetability(self) -> None:
        social_svc = SocialService(repository=SocialRepository(db_path=":memory:"))
        social_svc.update_presence("m1", "MasterHero", 85, True, "zone_tang_kiem_nhai", "Wilderness")
        social_svc.update_presence("m2", "SecretAgent", 70, True, "zone_secret_chamber", "Mật Thất")
        social_svc.update_presence("m3", "OfflineGuy", 60, False, "offline", "Offline")

        roster_svc = GuildRosterService(social_service=social_svc)
        # Register GuildMember instances from guild_stash_service
        roster_svc.register_guild_member("guild_shadow", GuildMember("m1", "MasterHero", GuildRole.GUILD_MASTER))
        roster_svc.register_guild_member("guild_shadow", GuildMember("m2", "SecretAgent", GuildRole.OFFICER))
        roster_svc.register_guild_member("guild_shadow", GuildMember("m3", "OfflineGuy", GuildRole.MEMBER))

        members = roster_svc.get_guild_roster_with_presence("guild_shadow")
        assert len(members) == 3

        m1_dto = next(m for m in members if m.player_id == "m1")
        assert m1_dto.role == "Đầu Lĩnh"
        assert m1_dto.level == 85
        assert m1_dto.is_online is True
        assert m1_dto.is_teleport_targetable is True  # Wilderness is targetable

        m2_dto = next(m for m in members if m.player_id == "m2")
        assert m2_dto.role == "Trưởng Lão"
        assert m2_dto.level == 70
        assert m2_dto.is_online is True
        assert m2_dto.is_teleport_targetable is False  # Secret chamber is NOT targetable

        m3_dto = next(m for m in members if m.player_id == "m3")
        assert m3_dto.role == "Huynh Đệ"
        assert m3_dto.is_online is False
        assert m3_dto.is_teleport_targetable is False  # Offline is NOT targetable

    def test_empty_guild_roster(self) -> None:
        roster_svc = GuildRosterService()
        assert roster_svc.get_guild_roster_with_presence("nonexistent") == []
