"""
Tier 1 E2E Tests: Friends & Social Engine, Presence, Guild Roster,
Safe Teleportation Channel (3.5s & Interrupts), and 5-Gate Destination Validation (R2, R3).
"""

from __future__ import annotations
import pytest
from tests.e2e_social_party.test_harness import (
    GuildMemberPresenceDTO,
    GuildRosterService,
    InterruptReason,
    PartyService,
    SocialService,
    TargetType,
    TeleportService,
)


@pytest.fixture
def social_env():
    party_svc = PartyService()
    social_svc = SocialService()
    guild_svc = GuildRosterService()
    teleport_svc = TeleportService(party_svc, social_svc, guild_svc)
    return party_svc, social_svc, guild_svc, teleport_svc


class TestTier1FriendLifecycle:
    """Feature 7: Friend Management Lifecycle (Request, Accept, Decline, Remove, Block)."""

    def test_friend_request_and_accept_flow(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        assert social_svc.send_friend_request("alice", "bob") is True
        assert "alice" in social_svc.friend_requests["bob"]
        assert social_svc.accept_friend_request("bob", "alice") is True
        assert social_svc.is_friend("alice", "bob") is True
        assert social_svc.is_friend("bob", "alice") is True

    def test_friend_request_decline_flow(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        social_svc.send_friend_request("alice", "bob")
        assert social_svc.decline_friend_request("bob", "alice") is True
        assert social_svc.is_friend("alice", "bob") is False

    def test_friend_removal(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        social_svc.send_friend_request("alice", "bob")
        social_svc.accept_friend_request("bob", "alice")
        assert social_svc.remove_friend("alice", "bob") is True
        assert social_svc.is_friend("alice", "bob") is False
        assert social_svc.is_friend("bob", "alice") is False

    def test_friend_bidirectional_block(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        social_svc.send_friend_request("alice", "bob")
        social_svc.accept_friend_request("bob", "alice")
        social_svc.block_player("alice", "bob")
        # Alice blocked Bob -> neither can see each other as friend
        assert social_svc.is_blocked("alice", "bob") is True
        assert social_svc.is_blocked("bob", "alice") is True
        assert social_svc.is_friend("alice", "bob") is False
        assert social_svc.send_friend_request("bob", "alice") is False

    def test_self_friend_request_rejected(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        assert social_svc.send_friend_request("alice", "alice") is False


class TestTier1RealTimePresence:
    """Feature 8: Real-Time Social Presence (Status, Level, Zone, Friend Note)."""

    def test_presence_online_offline_tracking(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        social_svc.send_friend_request("alice", "bob")
        social_svc.accept_friend_request("bob", "alice")
        social_svc.update_presence("bob", "Bob", 45, True, "zone_tang_kiem_nhai", "Tàng Kiếm Nhai")
        p_list = social_svc.get_friend_presence("alice")
        assert len(p_list) == 1
        assert p_list[0].is_online is True
        assert p_list[0].level == 45
        assert p_list[0].zone_id == "zone_tang_kiem_nhai"

    def test_friend_note_setting_and_retrieval(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        social_svc.send_friend_request("alice", "bob")
        social_svc.accept_friend_request("bob", "alice")
        social_svc.update_presence("bob", "Bob", 45, True, "zone_wild", "Wilderness")
        assert social_svc.set_friend_note("alice", "bob", "Tanker / Huyết Thể") is True
        pres = social_svc.get_friend_presence("alice")
        assert pres[0].note == "Tanker / Huyết Thể"

    def test_non_friend_note_rejected(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        assert social_svc.set_friend_note("alice", "stranger", "Note") is False

    def test_blocked_player_presence_hidden(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        social_svc.send_friend_request("alice", "bob")
        social_svc.accept_friend_request("bob", "alice")
        social_svc.update_presence("bob", "Bob", 45, True, "zone_wild", "Wild")
        social_svc.block_player("alice", "bob")
        # Alice blocked Bob -> presence list is empty
        assert len(social_svc.get_friend_presence("alice")) == 0

    def test_offline_friend_presence_reflection(self, social_env) -> None:
        _, social_svc, _, _ = social_env
        social_svc.send_friend_request("alice", "bob")
        social_svc.accept_friend_request("bob", "alice")
        social_svc.update_presence("bob", "Bob", 45, False, "offline", "Offline")
        pres = social_svc.get_friend_presence("alice")
        assert pres[0].is_online is False


class TestTier1GuildRosterIntegration:
    """Feature 9: Lightweight Guild Roster Query with Presence & Teleport Targetability."""

    def test_guild_roster_query_members(self, social_env) -> None:
        _, _, guild_svc, _ = social_env
        m1 = GuildMemberPresenceDTO("g1", "Leader", 80, "Đầu Lĩnh", True, "zone_sanctuary")
        m2 = GuildMemberPresenceDTO("g2", "Elder", 70, "Trưởng Lão", True, "zone_wild")
        guild_svc.register_member("guild_outcasts", m1)
        guild_svc.register_member("guild_outcasts", m2)
        roster = guild_svc.get_guild_roster_with_presence("guild_outcasts")
        assert len(roster) == 2
        assert {m.player_id for m in roster} == {"g1", "g2"}

    def test_guild_member_ranks(self, social_env) -> None:
        _, _, guild_svc, _ = social_env
        m = GuildMemberPresenceDTO("g3", "Bro", 50, "Huynh Đệ", True, "zone_hideout")
        guild_svc.register_member("guild_outcasts", m)
        roster = guild_svc.get_guild_roster_with_presence("guild_outcasts")
        assert any(x.rank == "Huynh Đệ" for x in roster)

    def test_empty_guild_roster(self, social_env) -> None:
        _, _, guild_svc, _ = social_env
        assert guild_svc.get_guild_roster_with_presence("nonexistent_guild") == []

    def test_guild_member_offline_status(self, social_env) -> None:
        _, _, guild_svc, _ = social_env
        m = GuildMemberPresenceDTO("g4", "Sleeper", 60, "Huynh Đệ", False, "zone_none")
        guild_svc.register_member("g_sleep", m)
        roster = guild_svc.get_guild_roster_with_presence("g_sleep")
        assert roster[0].is_online is False

    def test_guild_roster_teleport_targetable(self, social_env) -> None:
        _, _, guild_svc, _ = social_env
        m = GuildMemberPresenceDTO("g5", "TargetMate", 75, "Trưởng Lão", True, "zone_tang_kiem_nhai")
        guild_svc.register_member("g_active", m)
        assert any(x.player_id == "g5" for x in guild_svc.get_guild_roster_with_presence("g_active"))


class TestTier1TeleportChannelAndInterrupts:
    """Feature 10: Safe Teleportation Channel (3.5s Cast, Move & Damage Interrupts)."""

    def test_teleport_channel_starts_successfully(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        ok, chan_id = teleport_svc.start_teleport_channel("p1", TargetType.PARTY_MEMBER, "p2", "zone_wild")
        assert ok is True and chan_id.startswith("chan_p1_")

    def test_teleport_channel_interrupt_on_movement(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        teleport_svc.start_teleport_channel("p1", TargetType.PARTY_MEMBER, "p2", "zone_wild")
        interrupt = teleport_svc.on_player_moved("p1", mag=0.06)  # mag > 0.05
        assert interrupt == InterruptReason.INTERRUPT_MOVEMENT
        assert "p1" not in teleport_svc.active_channels

    def test_teleport_channel_movement_under_threshold_ignored(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        teleport_svc.start_teleport_channel("p1", TargetType.PARTY_MEMBER, "p2", "zone_wild")
        interrupt = teleport_svc.on_player_moved("p1", mag=0.03)  # mag <= 0.05
        assert interrupt is None
        assert "p1" in teleport_svc.active_channels

    def test_teleport_channel_interrupt_on_damage(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        teleport_svc.start_teleport_channel("p1", TargetType.PARTY_MEMBER, "p2", "zone_wild")
        interrupt = teleport_svc.on_player_damaged("p1", damage=10.0)
        assert interrupt == InterruptReason.INTERRUPT_DAMAGE
        assert "p1" not in teleport_svc.active_channels

    def test_teleport_channel_completion_after_full_duration(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        teleport_svc.start_teleport_channel("p1", TargetType.PARTY_MEMBER, "p2", "zone_wild")
        # Elapsed 3.5s -> completes
        ok, dest_zone = teleport_svc.complete_channel("p1", elapsed_time=3.5)
        assert ok is True and dest_zone == "zone_wild"


class TestTier1FiveGateDestinationValidation:
    """Feature 11: 5-Gate Destination Validation (Liveness, Relationship, Capacity, Lifecycle, Quarantine)."""

    def test_gate_1_target_dead_rejected(self, social_env) -> None:
        _, _, _, teleport_svc = social_env
        ok, reason = teleport_svc.validate_destination_5_gates("p1", "p2", TargetType.PARTY_MEMBER, "zone_wild", target_alive=False)
        assert ok is False and "GATE_1_TARGET_DEAD" in reason

    def test_gate_2_relationship_unauthorized_rejected(self, social_env) -> None:
        _, _, _, teleport_svc = social_env
        # Stranger who is not in party or friends
        ok, reason = teleport_svc.validate_destination_5_gates("p1", "stranger_99", TargetType.PARTY_MEMBER, "zone_wild")
        assert ok is False and "GATE_2_RELATIONSHIP" in reason

    def test_gate_3_zone_capacity_exceeded_rejected(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        teleport_svc.set_zone_state("zone_full_map", player_count=6, max_capacity=6)
        ok, reason = teleport_svc.validate_destination_5_gates("p1", "p2", TargetType.PARTY_MEMBER, "zone_full_map")
        assert ok is False and "GATE_3_CAPACITY" in reason

    def test_gate_4_lifecycle_closed_rejected(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        teleport_svc.set_zone_state("zone_expired", is_active=False)
        ok, reason = teleport_svc.validate_destination_5_gates("p1", "p2", TargetType.PARTY_MEMBER, "zone_expired")
        assert ok is False and "GATE_4_LIFECYCLE" in reason

    def test_gate_5_quarantine_boss_or_secret_rejected(self, social_env) -> None:
        party_svc, _, _, teleport_svc = social_env
        p = party_svc.create_party("p1", "Caster", 30)
        party_svc.invite_player(p.party_id, "p1", "p2")
        party_svc.accept_invite(p.party_id, "p2", "Target", 30)
        teleport_svc.set_zone_state("zone_boss_arena", is_boss_active=True)
        ok, reason = teleport_svc.validate_destination_5_gates("p1", "p2", TargetType.PARTY_MEMBER, "zone_boss_arena")
        assert ok is False and "GATE_5_QUARANTINE" in reason
