"""Integration test — two Alive instances exchanging skills on localhost."""

import json
import threading
import time
from http.server import HTTPServer
from pathlib import Path
from unittest.mock import patch, MagicMock, AsyncMock

import httpx
import pytest

from alive.core.skill_registry import SkillInfo


def _find_free_port():
    import socket
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("", 0))
        return s.getsockname()[1]


@pytest.fixture
def instance_a(tmp_path):
    """Instance A: has skills, runs HTTP server."""
    port = _find_free_port()
    skills_dir = tmp_path / "a" / "skills"
    skill_dir = skills_dir / "web_fetch"
    skill_dir.mkdir(parents=True)

    code = 'def run(input_data):\n    return {"url": input_data.get("url", ""), "status": 200}\n'
    (skill_dir / "skill.py").write_text(code)
    (skill_dir / "test_skill.py").write_text("def test_run():\n    pass\n")
    (skill_dir / "SKILL.md").write_text("# web_fetch\nFetch a URL.")
    (skill_dir / "metadata.json").write_text(json.dumps({
        "name": "web_fetch",
        "description": "Fetch a URL",
        "generation": 1,
        "lineage": [],
        "network_evolved": False,
        "origin": "self-built",
        "dependencies": [],
        "confidence_score": 0.85,
        "times_used": 10,
        "created_at": "2026-03-18T00:00:00",
        "success_count": 9,
        "fail_count": 1,
        "last_used": "",
        "input_schema": {},
        "output_schema": {},
        "sample_input": {},
        "domain": "web",
        "skill_type": "skill",
    }))

    mock_registry = MagicMock()
    mock_registry.list_skills_only.return_value = [
        SkillInfo(name="web_fetch", description="Fetch a URL", confidence_score=0.85, times_used=10),
    ]
    mock_registry.get_skill.return_value = SkillInfo(
        name="web_fetch", description="Fetch a URL", confidence_score=0.85,
    )
    mock_registry._skills_dir = skills_dir

    from alive.network.server import ALPHandler
    server = HTTPServer(("127.0.0.1", port), ALPHandler)
    server.timeout = 1.0
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    time.sleep(0.1)

    yield {
        "port": port,
        "server": server,
        "registry": mock_registry,
        "skills_dir": skills_dir,
        "identity": {"agent_id": "alp-instance-a", "alias": "darsh"},
    }

    server.shutdown()


@pytest.fixture
def instance_b(tmp_path):
    """Instance B: pulls skills from A."""
    skills_dir = tmp_path / "b" / "skills"
    skills_dir.mkdir(parents=True)

    mock_registry = MagicMock()
    mock_registry.list_skills_only.return_value = []
    mock_registry._skills_dir = skills_dir

    yield {
        "registry": mock_registry,
        "skills_dir": skills_dir,
        "identity": {"agent_id": "alp-instance-b", "alias": "friend"},
    }


class TestTwoInstanceExchange:
    def test_browse_skills(self, instance_a):
        """Instance B can browse Instance A's skills."""
        with patch("alive.core.skill_registry.get_registry", return_value=instance_a["registry"]), \
             patch("alive.network.conversation_log.log_event"):
            resp = httpx.get(f"http://127.0.0.1:{instance_a['port']}/alp/skills")

        assert resp.status_code == 200
        skills = resp.json()
        assert len(skills) == 1
        assert skills[0]["name"] == "web_fetch"
        assert skills[0]["confidence"] == 0.85

    def test_fetch_agent_card(self, instance_a):
        """Instance B can fetch Instance A's agent card."""
        mock_card = {
            "agent_id": "alp-instance-a",
            "alias": "darsh",
            "skills": [{"name": "web_fetch", "description": "Fetch a URL", "confidence": 0.85, "generation": 1}],
            "skill_count": 1,
            "integrations": ["web_search"],
            "alp_version": "0.1.0",
            "uptime_seconds": 100,
        }
        with patch("alive.network.identity.get_agent_card", return_value=mock_card), \
             patch("alive.network.conversation_log.log_event"):
            resp = httpx.get(f"http://127.0.0.1:{instance_a['port']}/alp/card")

        assert resp.status_code == 200
        card = resp.json()
        assert card["agent_id"] == "alp-instance-a"
        assert card["skill_count"] == 1

    def test_fetch_full_skill(self, instance_a):
        """Instance B can fetch the full skill package from Instance A."""
        with patch("alive.core.skill_registry.get_registry", return_value=instance_a["registry"]), \
             patch("alive.network.conversation_log.log_event"):
            resp = httpx.get(f"http://127.0.0.1:{instance_a['port']}/alp/skill/web_fetch")

        assert resp.status_code == 200
        pkg = resp.json()
        assert pkg["name"] == "web_fetch"
        assert "def run" in pkg["code"]
        assert pkg["generation"] == 1
        assert pkg["lineage"] == []

    @pytest.mark.asyncio
    async def test_full_pull_flow(self, instance_a, instance_b):
        """Instance B pulls a skill from A: fetch → adapt → test → save with lineage."""
        from alive.network.exchange import pull_skill
        from alive.sandbox.runner import SkillResult

        mock_test_result = SkillResult(
            success=True, output={"url": "", "status": 200}, error=None, duration_ms=50,
        )

        # The server endpoint uses get_registry for reading A's skills.
        # The exchange pull_skill uses get_registry for saving to B.
        # We use a side_effect that returns different registries depending on context.
        call_count = [0]
        def registry_side_effect():
            call_count[0] += 1
            # Server calls come first (from HTTP handler), then exchange calls
            return instance_b["registry"]

        with patch("alive.core.skill_registry.get_registry") as mock_get_reg, \
             patch("alive.network.conversation_log.log_event"), \
             patch("alive.network.exchange.adapt_skill", new_callable=AsyncMock) as mock_adapt, \
             patch("alive.sandbox.runner.run_skill_code", return_value=mock_test_result), \
             patch("alive.network.identity.get_or_create_identity", return_value=instance_b["identity"]):

            # Server handler reads instance_a's registry for /alp/skill endpoint
            # Exchange reads instance_b's registry for saving
            # Since both import from same source, we use instance_a's for the server
            # and override for exchange
            mock_get_reg.return_value = instance_a["registry"]

            # For exchange.py's get_registry() call, we temporarily override
            # Actually, the httpx.get inside pull_skill hits our REAL server which calls
            # the real get_registry (which is now mocked to return instance_a's registry).
            # Then when pull_skill calls get_registry() internally, it also gets instance_a.
            # We need a smarter approach: let server use instance_a, exchange use instance_b.
            registries = iter([instance_a["registry"], instance_b["registry"]])
            mock_get_reg.side_effect = lambda: next(registries, instance_b["registry"])

            mock_adapt.return_value = 'def run(input_data):\n    return {"url": input_data.get("url", ""), "status": 200}\n'

            result = await pull_skill(
                "127.0.0.1", instance_a["port"], "web_fetch", auto_approve=True,
            )

        assert result is not None
        assert result["generation"] == 2  # Was gen 1, now gen 2
        assert "alp-instance-b" in result["lineage"]
        assert result["network_evolved"] is True
        assert result["received_from"] == f"127.0.0.1:{instance_a['port']}"

        # Verify register was called on B's registry
        instance_b["registry"].register.assert_called_once()

    @pytest.mark.asyncio
    async def test_generation_increments_through_chain(self, instance_a, instance_b):
        """Verify generation increments: A(gen1) → B(gen2)."""
        from alive.network.exchange import pull_skill
        from alive.sandbox.runner import SkillResult

        mock_test = SkillResult(success=True, output={}, error=None, duration_ms=10)

        with patch("alive.core.skill_registry.get_registry") as mock_get_reg, \
             patch("alive.network.conversation_log.log_event"), \
             patch("alive.network.exchange.adapt_skill", new_callable=AsyncMock, return_value="def run(d):\n    return {}"), \
             patch("alive.sandbox.runner.run_skill_code", return_value=mock_test), \
             patch("alive.network.identity.get_or_create_identity", return_value=instance_b["identity"]):

            registries = iter([instance_a["registry"], instance_b["registry"]])
            mock_get_reg.side_effect = lambda: next(registries, instance_b["registry"])

            result = await pull_skill(
                "127.0.0.1", instance_a["port"], "web_fetch", auto_approve=True,
            )

        assert result["generation"] == 2
        assert len(result["lineage"]) == 1
        assert result["lineage"][-1] == "alp-instance-b"
