"""Tests for alive.network.exchange — skill pull, adapt, and lineage."""

import json
from unittest.mock import patch, MagicMock, AsyncMock
from pathlib import Path

import pytest


class TestDescribeMutations:
    def test_detects_telegram(self):
        from alive.network.exchange import describe_mutations

        original = "def run(input_data):\n    return {}"
        adapted = "from alive.integrations.telegram import send_telegram\ndef run(input_data):\n    send_telegram('hi')\n    return {}"
        muts = describe_mutations(original, adapted)
        assert "+telegram alerts" in muts

    def test_detects_webhook(self):
        from alive.network.exchange import describe_mutations

        original = "def run(input_data):\n    return {}"
        adapted = "send_webhook('alert')\ndef run(input_data):\n    return {}"
        muts = describe_mutations(original, adapted)
        assert "+webhook notifications" in muts

    def test_detects_browser(self):
        from alive.network.exchange import describe_mutations

        original = "def run(input_data):\n    return {}"
        adapted = "with BrowserTool() as bt:\n    pass\ndef run(input_data):\n    return {}"
        muts = describe_mutations(original, adapted)
        assert "+browser automation" in muts

    def test_detects_web_search(self):
        from alive.network.exchange import describe_mutations

        original = "def run(input_data):\n    return {}"
        adapted = "results = search_web('query')\ndef run(input_data):\n    return {}"
        muts = describe_mutations(original, adapted)
        assert "+web search" in muts

    def test_detects_memory(self):
        from alive.network.exchange import describe_mutations

        original = "def run(input_data):\n    return {}"
        adapted = "mem = SkillMemory('test')\ndef run(input_data):\n    return {}"
        muts = describe_mutations(original, adapted)
        assert "+persistent memory" in muts

    def test_detects_timezone(self):
        from alive.network.exchange import describe_mutations

        original = "import datetime\ntz = 'UTC'\ndef run(input_data):\n    return {}"
        adapted = "import datetime\ntz = 'IST'\ndef run(input_data):\n    return {}"
        muts = describe_mutations(original, adapted)
        assert "+local timezone" in muts

    def test_minor_adaptations_default(self):
        from alive.network.exchange import describe_mutations

        original = "def run(input_data):\n    return {'a': 1}"
        adapted = "def run(input_data):\n    return {'a': 2}"
        muts = describe_mutations(original, adapted)
        assert "minor adaptations" in muts

    def test_no_change(self):
        from alive.network.exchange import describe_mutations

        code = "def run(input_data):\n    return {}"
        muts = describe_mutations(code, code)
        assert "minor adaptations" in muts


class TestAdaptSkill:
    @pytest.mark.asyncio
    async def test_returns_adapted_code(self):
        from alive.network.exchange import adapt_skill

        mock_llm = AsyncMock()
        mock_llm.complete.return_value = "```python\ndef run(input_data):\n    return {'adapted': True}\n```"

        with patch("alive.llm.get_provider", return_value=mock_llm), \
             patch("alive.integrations.get_available_integrations", return_value={}), \
             patch("alive.core.skill_registry.get_registry") as mock_reg:
            mock_reg.return_value.list_skills_only.return_value = []
            result = await adapt_skill("def run(input_data):\n    return {}")

        assert "adapted" in result
        assert "```" not in result

    @pytest.mark.asyncio
    async def test_falls_back_on_error(self):
        from alive.network.exchange import adapt_skill

        original = "def run(input_data):\n    return {}"
        with patch("alive.llm.registry.get_provider", side_effect=Exception("no LLM")):
            result = await adapt_skill(original)
        assert result == original


class TestPullSkill:
    @pytest.mark.asyncio
    async def test_pull_success(self, tmp_path):
        from alive.network.exchange import pull_skill

        foreign = {
            "name": "test_skill",
            "code": "def run(input_data):\n    return {'ok': True}\n",
            "test_code": None,
            "metadata": {"dependencies": [], "confidence_score": 0.8},
            "birth_story": None,
            "description": "A test skill",
            "generation": 2,
            "lineage": ["alp-origin"],
        }

        mock_resp = MagicMock()
        mock_resp.json.return_value = foreign
        mock_resp.raise_for_status = MagicMock()

        from alive.sandbox.runner import SkillResult
        mock_test_result = SkillResult(success=True, output={"ok": True}, error=None, duration_ms=50)

        mock_identity = {"agent_id": "alp-me", "alias": "me"}

        skills_dir = tmp_path / "skills"
        skills_dir.mkdir()
        mock_registry = MagicMock()
        mock_registry._skills_dir = skills_dir

        with patch("alive.network.exchange.httpx") as mock_httpx, \
             patch("alive.network.exchange.adapt_skill", new_callable=AsyncMock, return_value=foreign["code"]), \
             patch("alive.sandbox.runner.run_skill_code", return_value=mock_test_result), \
             patch("alive.network.identity.get_or_create_identity", return_value=mock_identity), \
             patch("alive.core.skill_registry.get_registry", return_value=mock_registry), \
             patch("alive.network.conversation_log.log_event"):
            mock_httpx.get.return_value = mock_resp

            result = await pull_skill("127.0.0.1", 8484, "test_skill", auto_approve=True)

        assert result is not None
        assert result["generation"] == 3
        assert "alp-me" in result["lineage"]
        mock_registry.register.assert_called_once()

    @pytest.mark.asyncio
    async def test_pull_fetch_failure(self):
        from alive.network.exchange import pull_skill

        with patch("alive.network.exchange.httpx") as mock_httpx, \
             patch("alive.network.conversation_log.log_event"):
            mock_httpx.get.side_effect = Exception("connection refused")
            result = await pull_skill("127.0.0.1", 9999, "bad_skill", auto_approve=True)

        assert result is None

    @pytest.mark.asyncio
    async def test_pull_test_failure(self, tmp_path):
        from alive.network.exchange import pull_skill

        foreign = {
            "name": "broken_skill",
            "code": "def run():\n    raise Exception('broken')\n",
            "test_code": None,
            "metadata": {},
            "birth_story": None,
            "description": "",
            "generation": 1,
            "lineage": [],
        }

        mock_resp = MagicMock()
        mock_resp.json.return_value = foreign
        mock_resp.raise_for_status = MagicMock()

        from alive.sandbox.runner import SkillResult
        fail_result = SkillResult(success=False, output=None, error="broken", duration_ms=10)

        with patch("alive.network.exchange.httpx") as mock_httpx, \
             patch("alive.network.exchange.adapt_skill", new_callable=AsyncMock, return_value=foreign["code"]), \
             patch("alive.sandbox.runner.run_skill_code", return_value=fail_result), \
             patch("alive.network.conversation_log.log_event"):
            mock_httpx.get.return_value = mock_resp
            result = await pull_skill("127.0.0.1", 8484, "broken_skill", auto_approve=True)

        assert result is None
