"""
Tests for Phase 48: Agent Groups — Team/Group Management.
"""

import json
import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all group storage to tmp_path so tests are isolated."""
    groups_dir = tmp_path / "groups"
    channels_dir = tmp_path / "channels"
    tasks_dir = tmp_path / "tasks"
    profiles_dir = tmp_path / "profiles"
    for d in (groups_dir, channels_dir, tasks_dir, profiles_dir):
        d.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.agent_groups as ag
    monkeypatch.setattr(ag, "GROUPS_DIR", groups_dir)
    monkeypatch.setattr(ag, "ACTIVITY_LOG", groups_dir / "activity.jsonl")
    monkeypatch.setattr(ag, "CHANNELS_DIR", channels_dir)
    monkeypatch.setattr(ag, "TASKS_DIR", tasks_dir)
    monkeypatch.setattr(ag, "BROKER_DIR", tmp_path)


# ── 1. Create Group ───────────────────────────────────────────────────────


class TestCreateGroup:
    def test_create_basic(self):
        from brynq.agent_groups import create_group
        g = create_group("Alpha Team")
        assert g["group_id"]
        assert g["name"] == "Alpha Team"
        assert g["group_type"] == "team"
        assert g["visibility"] == "public"
        assert g["channel"] == "group-alpha-team"

    def test_create_with_owner(self):
        from brynq.agent_groups import create_group
        g = create_group("Beta Squad", owner_id="agent-1")
        assert "agent-1" in g["members"]
        assert g["members"]["agent-1"]["role"] == "owner"
        assert g["owner_id"] == "agent-1"

    def test_create_all_types(self):
        from brynq.agent_groups import create_group
        for t in ("team", "department", "project", "guild"):
            g = create_group(f"Test {t}", group_type=t)
            assert g["group_type"] == t

    def test_create_invalid_type(self):
        from brynq.agent_groups import create_group
        result = create_group("Bad", group_type="squad")
        assert "error" in result

    def test_create_empty_name(self):
        from brynq.agent_groups import create_group
        result = create_group("")
        assert "error" in result

    def test_create_private(self):
        from brynq.agent_groups import create_group
        g = create_group("Secret Ops", visibility="private")
        assert g["visibility"] == "private"

    def test_create_invalid_visibility(self):
        from brynq.agent_groups import create_group
        result = create_group("Bad Vis", visibility="hidden")
        assert "error" in result


# ── 2. Membership ─────────────────────────────────────────────────────────


class TestMembership:
    def test_add_member(self):
        from brynq.agent_groups import create_group, add_member
        g = create_group("Team A")
        result = add_member(g["group_id"], "agent-1")
        assert result["status"] == "added"
        assert result["role"] == "member"

    def test_add_member_with_role(self):
        from brynq.agent_groups import create_group, add_member
        g = create_group("Team B")
        result = add_member(g["group_id"], "agent-2", role="admin")
        assert result["role"] == "admin"

    def test_add_duplicate_member(self):
        from brynq.agent_groups import create_group, add_member
        g = create_group("Team C")
        add_member(g["group_id"], "agent-1")
        result = add_member(g["group_id"], "agent-1")
        assert "error" in result

    def test_add_invalid_role(self):
        from brynq.agent_groups import create_group, add_member
        g = create_group("Team D")
        result = add_member(g["group_id"], "agent-1", role="supreme_leader")
        assert "error" in result

    def test_add_member_nonexistent_group(self):
        from brynq.agent_groups import add_member
        result = add_member("nonexistent", "agent-1")
        assert "error" in result

    def test_remove_member(self):
        from brynq.agent_groups import create_group, add_member, remove_member, list_members
        g = create_group("Team E")
        add_member(g["group_id"], "agent-1")
        result = remove_member(g["group_id"], "agent-1")
        assert result["status"] == "removed"
        members = list_members(g["group_id"])
        assert len(members) == 0

    def test_remove_nonmember(self):
        from brynq.agent_groups import create_group, remove_member
        g = create_group("Team F")
        result = remove_member(g["group_id"], "ghost")
        assert "error" in result

    def test_list_members(self):
        from brynq.agent_groups import create_group, add_member, list_members
        g = create_group("Team G", owner_id="owner-1")
        add_member(g["group_id"], "agent-2")
        add_member(g["group_id"], "agent-3")
        members = list_members(g["group_id"])
        assert len(members) == 3  # owner + 2 added
        agent_ids = {m["agent_id"] for m in members}
        assert agent_ids == {"owner-1", "agent-2", "agent-3"}

    def test_list_members_nonexistent_group(self):
        from brynq.agent_groups import list_members
        result = list_members("nope")
        assert "error" in result

    def test_get_member_groups(self):
        from brynq.agent_groups import create_group, add_member, get_member_groups
        g1 = create_group("Team H")
        g2 = create_group("Team I")
        add_member(g1["group_id"], "agent-x")
        add_member(g2["group_id"], "agent-x")
        groups = get_member_groups("agent-x")
        assert len(groups) == 2
        gids = {g["group_id"] for g in groups}
        assert g1["group_id"] in gids
        assert g2["group_id"] in gids

    def test_get_member_groups_empty(self):
        from brynq.agent_groups import get_member_groups
        groups = get_member_groups("nobody")
        assert groups == []


# ── 3. Group Channel ──────────────────────────────────────────────────────


class TestGroupChannel:
    def test_get_group_channel(self):
        from brynq.agent_groups import create_group, get_group_channel
        g = create_group("Chat Team")
        ch = get_group_channel(g["group_id"])
        assert ch["channel"] == "group-chat-team"

    def test_get_group_channel_nonexistent(self):
        from brynq.agent_groups import get_group_channel
        result = get_group_channel("nope")
        assert "error" in result

    def test_post_to_channel_member(self):
        from brynq.agent_groups import create_group, add_member, post_to_group_channel
        g = create_group("Posting Team")
        add_member(g["group_id"], "agent-1")
        result = post_to_group_channel(g["group_id"], "agent-1", "Hello group!")
        assert result["status"] == "posted"
        assert result["channel"] == "group-posting-team"

    def test_post_to_channel_nonmember(self):
        from brynq.agent_groups import create_group, post_to_group_channel
        g = create_group("Exclusive Team")
        result = post_to_group_channel(g["group_id"], "outsider", "Let me in!")
        assert "error" in result

    def test_post_empty_message(self):
        from brynq.agent_groups import create_group, add_member, post_to_group_channel
        g = create_group("Empty Msg Team")
        add_member(g["group_id"], "agent-1")
        result = post_to_group_channel(g["group_id"], "agent-1", "")
        assert "error" in result

    def test_read_channel(self):
        from brynq.agent_groups import (
            create_group, add_member, post_to_group_channel, read_group_channel,
        )
        g = create_group("Read Team")
        add_member(g["group_id"], "agent-1")
        post_to_group_channel(g["group_id"], "agent-1", "msg1")
        post_to_group_channel(g["group_id"], "agent-1", "msg2")
        msgs = read_group_channel(g["group_id"], "agent-1")
        assert len(msgs) == 2
        assert msgs[0]["message"] == "msg1"

    def test_read_channel_nonmember(self):
        from brynq.agent_groups import create_group, read_group_channel
        g = create_group("Private Read Team")
        result = read_group_channel(g["group_id"], "outsider")
        assert "error" in result


# ── 4. Group Tasks ────────────────────────────────────────────────────────


class TestGroupTasks:
    def test_create_group_task(self):
        from brynq.agent_groups import create_group, create_group_task
        g = create_group("Task Team")
        task = create_group_task(g["group_id"], "Build the widget", "It needs to be good")
        assert task["task_id"]
        assert task["title"] == "Build the widget"
        assert task["group_id"] == g["group_id"]
        assert task["status"] == "open"

    def test_create_task_nonexistent_group(self):
        from brynq.agent_groups import create_group_task
        result = create_group_task("nope", "Task")
        assert "error" in result

    def test_create_task_empty_title(self):
        from brynq.agent_groups import create_group, create_group_task
        g = create_group("Task Team 2")
        result = create_group_task(g["group_id"], "")
        assert "error" in result

    def test_create_task_with_skills(self):
        from brynq.agent_groups import create_group, create_group_task
        g = create_group("Skill Team")
        task = create_group_task(g["group_id"], "Code review", required_skills=["python", "testing"])
        assert task["required_skills"] == ["python", "testing"]

    def test_list_group_tasks(self):
        from brynq.agent_groups import create_group, create_group_task, list_group_tasks
        g = create_group("List Team")
        create_group_task(g["group_id"], "Task 1")
        create_group_task(g["group_id"], "Task 2")
        tasks = list_group_tasks(g["group_id"])
        assert len(tasks) == 2

    def test_list_group_tasks_filter_status(self):
        from brynq.agent_groups import (
            create_group, create_group_task, list_group_tasks, complete_group_task,
        )
        g = create_group("Filter Team")
        t1 = create_group_task(g["group_id"], "Done task")
        create_group_task(g["group_id"], "Open task")
        complete_group_task(t1["task_id"], "agent-1")
        open_tasks = list_group_tasks(g["group_id"], status="open")
        assert len(open_tasks) == 1
        assert open_tasks[0]["title"] == "Open task"

    def test_complete_group_task(self):
        from brynq.agent_groups import create_group, create_group_task, complete_group_task
        g = create_group("Complete Team")
        t = create_group_task(g["group_id"], "Finish this")
        result = complete_group_task(t["task_id"], "agent-1")
        assert result["status"] == "completed"
        assert result["assignee_id"] == "agent-1"
        assert result["completed_at"] is not None

    def test_complete_already_completed(self):
        from brynq.agent_groups import create_group, create_group_task, complete_group_task
        g = create_group("Double Complete Team")
        t = create_group_task(g["group_id"], "Once is enough")
        complete_group_task(t["task_id"], "agent-1")
        result = complete_group_task(t["task_id"], "agent-2")
        assert "error" in result

    def test_complete_nonexistent_task(self):
        from brynq.agent_groups import complete_group_task
        result = complete_group_task("nope", "agent-1")
        assert "error" in result


# ── 5. Group Stats ────────────────────────────────────────────────────────


class TestGroupStats:
    def test_basic_stats(self):
        from brynq.agent_groups import (
            create_group, add_member, create_group_task, complete_group_task, get_group_stats,
        )
        g = create_group("Stats Team", owner_id="owner-1")
        add_member(g["group_id"], "agent-1")
        t1 = create_group_task(g["group_id"], "Task A")
        create_group_task(g["group_id"], "Task B")
        complete_group_task(t1["task_id"], "agent-1")

        stats = get_group_stats(g["group_id"])
        assert stats["member_count"] == 2
        assert stats["total_tasks"] == 2
        assert stats["completed_tasks"] == 1
        assert stats["active_tasks"] == 1
        assert len(stats["top_contributors"]) == 1
        assert stats["top_contributors"][0]["agent_id"] == "agent-1"

    def test_stats_nonexistent_group(self):
        from brynq.agent_groups import get_group_stats
        result = get_group_stats("nope")
        assert "error" in result

    def test_stats_with_reputation(self, tmp_path):
        from brynq.agent_groups import create_group, add_member, get_group_stats
        g = create_group("Rep Team")
        add_member(g["group_id"], "agent-r1")
        add_member(g["group_id"], "agent-r2")

        # Create fake profiles with reputation
        profiles_dir = tmp_path / "profiles"
        profiles_dir.mkdir(exist_ok=True)
        for aid, rep in [("agent-r1", 80), ("agent-r2", 90)]:
            profile = {"session_id": aid, "reputation": rep}
            (profiles_dir / f"{aid}.json").write_text(json.dumps(profile))

        stats = get_group_stats(g["group_id"])
        assert stats["group_reputation"] == 85.0


# ── 6. Group Discovery ───────────────────────────────────────────────────


class TestDiscovery:
    def test_list_groups(self):
        from brynq.agent_groups import create_group, list_groups
        create_group("Public A")
        create_group("Public B")
        groups = list_groups()
        assert len(groups) == 2

    def test_list_groups_by_type(self):
        from brynq.agent_groups import create_group, list_groups
        create_group("Guild A", group_type="guild")
        create_group("Team A", group_type="team")
        guilds = list_groups(group_type="guild")
        assert len(guilds) == 1
        assert guilds[0]["group_type"] == "guild"

    def test_list_groups_hides_private(self):
        from brynq.agent_groups import create_group, list_groups
        create_group("Public C")
        create_group("Secret", visibility="private")
        groups = list_groups()
        assert len(groups) == 1
        assert groups[0]["name"] == "Public C"

    def test_list_groups_include_private(self):
        from brynq.agent_groups import create_group, list_groups
        create_group("Public D")
        create_group("Private D", visibility="private")
        groups = list_groups(include_private=True)
        assert len(groups) == 2

    def test_search_groups_by_name(self):
        from brynq.agent_groups import create_group, search_groups
        create_group("Python Guild", group_type="guild")
        create_group("Rust Guild", group_type="guild")
        results = search_groups("python")
        assert len(results) == 1
        assert results[0]["name"] == "Python Guild"

    def test_search_groups_by_description(self):
        from brynq.agent_groups import create_group, search_groups
        create_group("Alpha", description="handles deployment pipelines")
        results = search_groups("deployment")
        assert len(results) == 1

    def test_search_empty_query(self):
        from brynq.agent_groups import search_groups
        assert search_groups("") == []

    def test_search_hides_private(self):
        from brynq.agent_groups import create_group, search_groups
        create_group("Secret Agents", visibility="private")
        results = search_groups("Secret")
        assert len(results) == 0

    def test_get_group(self):
        from brynq.agent_groups import create_group, get_group
        g = create_group("Fetch Me")
        loaded = get_group(g["group_id"])
        assert loaded["name"] == "Fetch Me"

    def test_get_group_nonexistent(self):
        from brynq.agent_groups import get_group
        assert get_group("nope") is None


# ── 7. Join / Leave ──────────────────────────────────────────────────────


class TestJoinLeave:
    def test_join_public_group(self):
        from brynq.agent_groups import create_group, join_group, list_members
        g = create_group("Open Door")
        result = join_group(g["group_id"], "agent-1")
        assert result["status"] == "joined"
        members = list_members(g["group_id"])
        assert any(m["agent_id"] == "agent-1" for m in members)

    def test_join_private_group_rejected(self):
        from brynq.agent_groups import create_group, join_group
        g = create_group("Closed Door", visibility="private")
        result = join_group(g["group_id"], "agent-1")
        assert "error" in result

    def test_join_already_member(self):
        from brynq.agent_groups import create_group, join_group
        g = create_group("Dupe Team")
        join_group(g["group_id"], "agent-1")
        result = join_group(g["group_id"], "agent-1")
        assert "error" in result

    def test_request_join_private(self):
        from brynq.agent_groups import create_group, request_join
        g = create_group("Private Club", visibility="private", owner_id="owner-1")
        result = request_join(g["group_id"], "agent-1", message="Please let me in")
        assert result["status"] == "requested"
        assert result["request_id"]

    def test_request_join_duplicate(self):
        from brynq.agent_groups import create_group, request_join
        g = create_group("No Dupes", visibility="private", owner_id="owner-1")
        request_join(g["group_id"], "agent-1")
        result = request_join(g["group_id"], "agent-1")
        assert "error" in result

    def test_approve_join(self):
        from brynq.agent_groups import create_group, request_join, approve_join, list_members
        g = create_group("Approval Team", visibility="private", owner_id="owner-1")
        request_join(g["group_id"], "agent-1")
        result = approve_join(g["group_id"], "agent-1", approver_id="owner-1")
        assert result["status"] == "approved"
        members = list_members(g["group_id"])
        agent_ids = {m["agent_id"] for m in members}
        assert "agent-1" in agent_ids

    def test_approve_by_non_admin(self):
        from brynq.agent_groups import create_group, add_member, request_join, approve_join
        g = create_group("Strict Team", visibility="private", owner_id="owner-1")
        add_member(g["group_id"], "regular-joe", role="member")
        request_join(g["group_id"], "agent-1")
        result = approve_join(g["group_id"], "agent-1", approver_id="regular-joe")
        assert "error" in result

    def test_approve_no_pending_request(self):
        from brynq.agent_groups import create_group, approve_join
        g = create_group("No Requests", owner_id="owner-1")
        result = approve_join(g["group_id"], "agent-1", approver_id="owner-1")
        assert "error" in result

    def test_leave_group(self):
        from brynq.agent_groups import create_group, join_group, leave_group, list_members
        g = create_group("Leave Team")
        join_group(g["group_id"], "agent-1")
        result = leave_group(g["group_id"], "agent-1")
        assert result["status"] == "left"
        members = list_members(g["group_id"])
        assert len(members) == 0

    def test_leave_nonmember(self):
        from brynq.agent_groups import create_group, leave_group
        g = create_group("Ghost Team")
        result = leave_group(g["group_id"], "nobody")
        assert "error" in result

    def test_join_nonexistent_group(self):
        from brynq.agent_groups import join_group
        result = join_group("fake-id", "agent-1")
        assert "error" in result


# ── Activity Log ──────────────────────────────────────────────────────────


class TestActivityLog:
    def test_activity_logged(self):
        from brynq.agent_groups import create_group, add_member, ACTIVITY_LOG
        from brynq.server import _read_jsonl_sync
        g = create_group("Logged Team", owner_id="owner-1")
        add_member(g["group_id"], "agent-1")
        entries = _read_jsonl_sync(ACTIVITY_LOG)
        actions = [e["action"] for e in entries]
        assert "group_created" in actions
        assert "member_added" in actions
