from __future__ import annotations

import json

import pytest
from hypothesis import given, settings
from hypothesis import strategies as st

from claude_teams.models import (
    COLOR_PALETTE,
    IdleNotification,
    InboxMessage,
    LeadMember,
    SendMessageResult,
    ShutdownApproved,
    ShutdownRequest,
    SpawnResult,
    TaskAssignment,
    TaskFile,
    TeamConfig,
    TeamCreateResult,
    TeamDeleteResult,
    TeammateMember,
)


class TestColorPalette:
    def test_has_8_colors(self):
        assert len(COLOR_PALETTE) == 8

    def test_blue_first(self):
        assert COLOR_PALETTE[0] == "blue"

    def test_all_expected_colors_present(self):
        expected = {"blue", "green", "yellow", "purple", "orange", "pink", "cyan", "red"}
        assert set(COLOR_PALETTE) == expected


class TestLeadMember:
    def test_serializes_with_camel_case_aliases(self):
        lead = LeadMember(
            agent_id="team-lead@my-team",
            name="team-lead",
            agent_type="team-lead",
            model="claude-opus-4-6",
            joined_at=1770398183858,
            cwd="/tmp/work",
        )
        data = lead.model_dump(by_alias=True)
        assert data["agentId"] == "team-lead@my-team"
        assert data["agentType"] == "team-lead"
        assert data["joinedAt"] == 1770398183858
        assert data["subscriptions"] == []

    def test_deserializes_from_camel_case_json(self):
        raw = {
            "agentId": "team-lead@my-team",
            "name": "team-lead",
            "agentType": "team-lead",
            "model": "claude-opus-4-6",
            "joinedAt": 1770398183858,
            "cwd": "/tmp/work",
            "subscriptions": [],
        }
        lead = LeadMember.model_validate(raw)
        assert lead.agent_id == "team-lead@my-team"
        assert lead.joined_at == 1770398183858

    def test_no_tmux_pane_id_field(self):
        """需求 3.5：LeadMember 不应包含 tmux_pane_id 字段"""
        lead = LeadMember(
            agent_id="team-lead@t",
            name="team-lead",
            agent_type="team-lead",
            model="sonnet",
            joined_at=0,
            cwd="/tmp",
        )
        assert not hasattr(lead, "tmux_pane_id")
        data = lead.model_dump(by_alias=True)
        assert "tmuxPaneId" not in data

    def test_old_tmux_pane_id_in_json_is_ignored(self):
        """旧配置文件中的 tmuxPaneId 字段应被忽略（向后兼容）"""
        raw = {
            "agentId": "team-lead@t",
            "name": "team-lead",
            "agentType": "team-lead",
            "model": "sonnet",
            "joinedAt": 0,
            "tmuxPaneId": "%5",
            "cwd": "/tmp",
        }
        lead = LeadMember.model_validate(raw)
        assert lead.agent_id == "team-lead@t"


class TestTeammateMember:
    def test_serializes_with_acpx_session_name(self):
        """需求 3.1：TeammateMember 应包含 acpx_session_name 字段"""
        mate = TeammateMember(
            agent_id="worker@my-team",
            name="worker",
            agent_type="general-purpose",
            model="sonnet",
            color="blue",
            plan_mode_required=False,
            joined_at=1770398210601,
            acpx_session_name="worker@my-team",
            cwd="/tmp/work",
            is_active=False,
        )
        data = mate.model_dump(by_alias=True)
        assert data["agentId"] == "worker@my-team"
        assert data["acpxSessionName"] == "worker@my-team"
        assert data["planModeRequired"] is False
        assert data["isActive"] is False

    def test_defaults(self):
        mate = TeammateMember(
            agent_id="w@t",
            name="w",
            agent_type="general-purpose",
            model="sonnet",
            color="blue",
            joined_at=0,
            acpx_session_name="w@t",
            cwd="/tmp",
        )
        assert mate.plan_mode_required is False
        assert mate.is_active is False
        assert mate.subscriptions == []

    def test_no_tmux_pane_id_field(self):
        """需求 3.1：TeammateMember 不应包含 tmux_pane_id 字段"""
        mate = TeammateMember(
            agent_id="w@t",
            name="w",
            agent_type="gp",
            model="sonnet",
            color="blue",
            joined_at=0,
            acpx_session_name="w@t",
            cwd="/tmp",
        )
        assert not hasattr(mate, "tmux_pane_id")
        data = mate.model_dump(by_alias=True)
        assert "tmuxPaneId" not in data

    def test_no_backend_type_field(self):
        """需求 3.3：TeammateMember 不应包含 backend_type 字段"""
        mate = TeammateMember(
            agent_id="w@t",
            name="w",
            agent_type="gp",
            model="sonnet",
            color="blue",
            joined_at=0,
            acpx_session_name="w@t",
            cwd="/tmp",
        )
        assert not hasattr(mate, "backend_type")
        data = mate.model_dump(by_alias=True)
        assert "backendType" not in data

    def test_no_opencode_session_id_field(self):
        """需求 3.1：TeammateMember 不应包含 opencode_session_id 字段"""
        mate = TeammateMember(
            agent_id="w@t",
            name="w",
            agent_type="gp",
            model="sonnet",
            color="blue",
            joined_at=0,
            acpx_session_name="w@t",
            cwd="/tmp",
        )
        assert not hasattr(mate, "opencode_session_id")
        data = mate.model_dump(by_alias=True)
        assert "opencodeSessionId" not in data

    def test_acpx_session_name_round_trip(self):
        """acpx_session_name 序列化/反序列化应保持一致"""
        raw = {
            "agentId": "worker@my-team",
            "name": "worker",
            "agentType": "gp",
            "model": "sonnet",
            "color": "blue",
            "joinedAt": 100,
            "acpxSessionName": "worker@my-team",
            "cwd": "/tmp",
        }
        mate = TeammateMember.model_validate(raw)
        assert mate.acpx_session_name == "worker@my-team"
        data = mate.model_dump(by_alias=True)
        assert data["acpxSessionName"] == "worker@my-team"

    def test_old_fields_in_json_are_ignored(self):
        """旧配置文件中的 tmuxPaneId/backendType/opencodeSessionId 应被忽略"""
        raw = {
            "agentId": "w@t",
            "name": "w",
            "agentType": "gp",
            "model": "sonnet",
            "color": "blue",
            "joinedAt": 0,
            "acpxSessionName": "w@t",
            "cwd": "/tmp",
            "tmuxPaneId": "%5",
            "backendType": "claude",
            "opencodeSessionId": "ses_x",
        }
        mate = TeammateMember.model_validate(raw)
        assert mate.acpx_session_name == "w@t"


class TestTeamConfig:
    def test_round_trip_with_lead_only(self):
        lead = LeadMember(
            agent_id="team-lead@test",
            name="team-lead",
            agent_type="team-lead",
            model="claude-opus-4-6",
            joined_at=1770398183858,
            cwd="/tmp",
        )
        config = TeamConfig(
            name="test",
            description="A test team",
            created_at=1770398183858,
            lead_agent_id="team-lead@test",
            lead_session_id="abc-123",
            members=[lead],
        )
        raw = json.loads(config.model_dump_json(by_alias=True))
        assert raw["createdAt"] == 1770398183858
        assert raw["leadAgentId"] == "team-lead@test"
        assert raw["leadSessionId"] == "abc-123"
        assert len(raw["members"]) == 1

    def test_deserializes_mixed_members(self):
        raw = {
            "name": "test",
            "description": "",
            "createdAt": 100,
            "leadAgentId": "team-lead@test",
            "leadSessionId": "sid",
            "members": [
                {
                    "agentId": "team-lead@test",
                    "name": "team-lead",
                    "agentType": "team-lead",
                    "model": "opus",
                    "joinedAt": 100,
                    "cwd": "/tmp",
                    "subscriptions": [],
                },
                {
                    "agentId": "worker@test",
                    "name": "worker",
                    "agentType": "general-purpose",
                    "model": "sonnet",
                    "color": "blue",
                    "planModeRequired": False,
                    "joinedAt": 200,
                    "acpxSessionName": "worker@test",
                    "cwd": "/tmp",
                    "subscriptions": [],
                    "isActive": False,
                },
            ],
        }
        config = TeamConfig.model_validate(raw)
        assert len(config.members) == 2
        assert isinstance(config.members[0], LeadMember)
        assert isinstance(config.members[1], TeammateMember)


class TestTaskFile:
    def test_initial_task_excludes_none_fields(self):
        task = TaskFile(id="1", subject="Do thing", description="Details")
        data = task.model_dump(by_alias=True, exclude_none=True)
        assert "owner" not in data
        assert "metadata" not in data
        assert data["id"] == "1"
        assert data["status"] == "pending"
        assert data["blockedBy"] == []

    def test_task_with_owner_includes_it(self):
        task = TaskFile(id="2", subject="s", description="d", owner="worker")
        data = task.model_dump(by_alias=True, exclude_none=True)
        assert data["owner"] == "worker"

    def test_id_is_string(self):
        task = TaskFile(id="1", subject="s", description="d")
        assert isinstance(task.id, str)


class TestInboxMessage:
    def test_serializes_with_from_alias(self):
        msg = InboxMessage(
            from_="team-lead",
            text="hello",
            timestamp="2026-02-06T17:18:04.701Z",
            read=False,
            summary="greeting",
        )
        data = msg.model_dump(by_alias=True, exclude_none=True)
        assert data["from"] == "team-lead"
        assert "color" not in data
        assert data["summary"] == "greeting"

    def test_optional_fields_excluded_when_none(self):
        msg = InboxMessage(from_="w", text="t", timestamp="ts")
        data = msg.model_dump(by_alias=True, exclude_none=True)
        assert "summary" not in data
        assert "color" not in data

    def test_with_color(self):
        msg = InboxMessage(from_="worker", text="done", timestamp="ts", color="blue", summary="status")
        data = msg.model_dump(by_alias=True, exclude_none=True)
        assert data["color"] == "blue"


class TestStructuredMessages:
    def test_idle_notification(self):
        n = IdleNotification(from_="worker", timestamp="2026-02-06T17:18:04.701Z")
        data = json.loads(n.model_dump_json(by_alias=True))
        assert data["type"] == "idle_notification"
        assert data["from"] == "worker"
        assert data["idleReason"] == "available"

    def test_task_assignment(self):
        a = TaskAssignment(
            task_id="1",
            subject="Do thing",
            description="Details",
            assigned_by="team-lead",
            timestamp="2026-02-06T17:18:04.701Z",
        )
        data = json.loads(a.model_dump_json(by_alias=True))
        assert data["type"] == "task_assignment"
        assert data["taskId"] == "1"
        assert data["assignedBy"] == "team-lead"

    def test_shutdown_request(self):
        r = ShutdownRequest(
            request_id="shutdown-1770398300000@worker",
            from_="team-lead",
            reason="Done",
            timestamp="ts",
        )
        data = json.loads(r.model_dump_json(by_alias=True))
        assert data["type"] == "shutdown_request"
        assert data["requestId"] == "shutdown-1770398300000@worker"
        assert data["from"] == "team-lead"

    def test_shutdown_approved_with_acpx_session_name(self):
        """需求 3.4：ShutdownApproved 应包含 acpx_session_name 字段"""
        a = ShutdownApproved(
            request_id="shutdown-123@worker",
            from_="worker",
            timestamp="ts",
            acpx_session_name="worker@my-team",
        )
        data = json.loads(a.model_dump_json(by_alias=True))
        assert data["type"] == "shutdown_approved"
        assert data["acpxSessionName"] == "worker@my-team"

    def test_shutdown_approved_no_pane_id(self):
        """需求 3.4：ShutdownApproved 不应包含 pane_id 字段"""
        a = ShutdownApproved(
            request_id="r", from_="w", timestamp="ts", acpx_session_name="w@t"
        )
        assert not hasattr(a, "pane_id")
        data = a.model_dump(by_alias=True)
        assert "paneId" not in data

    def test_shutdown_approved_no_backend_type(self):
        """需求 3.4：ShutdownApproved 不应包含 backend_type 字段"""
        a = ShutdownApproved(
            request_id="r", from_="w", timestamp="ts", acpx_session_name="w@t"
        )
        assert not hasattr(a, "backend_type")
        data = a.model_dump(by_alias=True)
        assert "backendType" not in data

    def test_shutdown_approved_no_session_id(self):
        """需求 3.4：ShutdownApproved 不应包含 session_id 字段"""
        a = ShutdownApproved(
            request_id="r", from_="w", timestamp="ts", acpx_session_name="w@t"
        )
        assert not hasattr(a, "session_id")
        data = a.model_dump(by_alias=True)
        assert "sessionId" not in data


class TestToolReturnModels:
    def test_team_create_result(self):
        r = TeamCreateResult(team_name="t", team_file_path="/p", lead_agent_id="team-lead@t")
        assert r.team_name == "t"

    def test_team_delete_result(self):
        r = TeamDeleteResult(success=True, message='Cleaned up directories for team "t"', team_name="t")
        assert r.success is True

    def test_spawn_result(self):
        r = SpawnResult(agent_id="w@t", name="w", team_name="t")
        assert r.message == "Session created. Agent is dead means ready — use send_message to assign a task."

    def test_send_message_result(self):
        r = SendMessageResult(success=True, message="sent")
        data = r.model_dump(exclude_none=True)
        assert "routing" not in data
        assert "request_id" not in data


# Feature: acpx-backend-migration, Property 2: Session Name 与 agent_id 一致性
# 验证：需求 2.1、2.2、2.3

_name_strategy = st.text(
    alphabet=st.characters(whitelist_categories=("Ll", "Lu", "Nd"), whitelist_characters="-"),
    min_size=1,
    max_size=20,
).filter(lambda s: s and not s.startswith("-") and not s.endswith("-"))

_team_name_strategy = _name_strategy


@given(name=_name_strategy, team_name=_team_name_strategy)
@settings(max_examples=100)
def test_property_acpx_session_name_equals_agent_id(name: str, team_name: str):
    """属性 2：对任意 name 和 team_name，acpx_session_name 应等于 agent_id（均为 <name>@<team_name>）"""
    expected = f"{name}@{team_name}"
    mate = TeammateMember(
        agent_id=expected,
        name=name,
        agent_type="general-purpose",
        model="sonnet",
        color="blue",
        joined_at=0,
        acpx_session_name=expected,
        cwd="/tmp",
    )
    assert mate.acpx_session_name == mate.agent_id
    assert mate.acpx_session_name == expected
    assert "@" in mate.acpx_session_name
    parts = mate.acpx_session_name.split("@", 1)
    assert parts[0] == name
    assert parts[1] == team_name
