"""Tests for alive.network.server — ALP HTTP server."""

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

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 server_port():
    return _find_free_port()


@pytest.fixture
def running_server(server_port, tmp_path):
    """Start a real ALP server on a random port for testing."""
    from alive.network.server import ALPHandler

    server = HTTPServer(("127.0.0.1", server_port), ALPHandler)
    server.timeout = 1.0
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    time.sleep(0.1)  # Let server start
    yield server_port
    server.shutdown()


class TestCardEndpoint:
    def test_returns_agent_card(self, running_server):
        mock_card = {
            "agent_id": "alp-test",
            "alias": "tester",
            "skills": [],
            "skill_count": 0,
            "integrations": [],
            "alp_version": "0.1.0",
            "uptime_seconds": 1,
        }
        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:{running_server}/alp/card")
        assert resp.status_code == 200
        data = resp.json()
        assert data["agent_id"] == "alp-test"


class TestSkillsEndpoint:
    def test_returns_skill_catalog(self, running_server, tmp_path):
        skills = [
            SkillInfo(name="skill_a", description="Skill A", confidence_score=0.9),
            SkillInfo(name="skill_b", description="Skill B", confidence_score=0.5),
        ]
        skills_dir = tmp_path / "skills"
        skills_dir.mkdir()
        for s in skills:
            d = skills_dir / s.name
            d.mkdir()
            (d / "metadata.json").write_text(json.dumps({"generation": 2}))

        mock_registry = MagicMock()
        mock_registry.list_skills_only.return_value = skills
        mock_registry._skills_dir = skills_dir

        with patch("alive.core.skill_registry.get_registry", return_value=mock_registry), \
             patch("alive.network.conversation_log.log_event"):
            resp = httpx.get(f"http://127.0.0.1:{running_server}/alp/skills")

        assert resp.status_code == 200
        data = resp.json()
        assert len(data) == 2
        assert data[0]["name"] == "skill_a"
        assert data[0]["generation"] == 2

    def test_empty_catalog(self, running_server, tmp_path):
        mock_registry = MagicMock()
        mock_registry.list_skills_only.return_value = []
        mock_registry._skills_dir = tmp_path

        with patch("alive.core.skill_registry.get_registry", return_value=mock_registry), \
             patch("alive.network.conversation_log.log_event"):
            resp = httpx.get(f"http://127.0.0.1:{running_server}/alp/skills")

        assert resp.status_code == 200
        assert resp.json() == []


class TestSkillDetailEndpoint:
    def test_returns_full_package(self, running_server, tmp_path):
        skills_dir = tmp_path / "skills"
        skill_dir = skills_dir / "price_checker"
        skill_dir.mkdir(parents=True)

        code = 'def run(input_data):\n    return {"price": 42}\n'
        test_code = 'def test_run():\n    pass\n'
        metadata = {"generation": 3, "lineage": ["alp-a", "alp-b"], "dependencies": []}
        birth_story = {"request": "check prices"}

        (skill_dir / "skill.py").write_text(code)
        (skill_dir / "test_skill.py").write_text(test_code)
        (skill_dir / "metadata.json").write_text(json.dumps(metadata))
        (skill_dir / "birth_story.json").write_text(json.dumps(birth_story))
        (skill_dir / "SKILL.md").write_text("# price_checker\nChecks prices.")

        mock_registry = MagicMock()
        mock_registry.get_skill.return_value = SkillInfo(name="price_checker", description="Checks prices")
        mock_registry._skills_dir = skills_dir

        with patch("alive.core.skill_registry.get_registry", return_value=mock_registry), \
             patch("alive.network.conversation_log.log_event"):
            resp = httpx.get(f"http://127.0.0.1:{running_server}/alp/skill/price_checker")

        assert resp.status_code == 200
        data = resp.json()
        assert data["name"] == "price_checker"
        assert data["code"] == code
        assert data["test_code"] == test_code
        assert data["generation"] == 3
        assert data["lineage"] == ["alp-a", "alp-b"]
        assert data["birth_story"] == birth_story

    def test_skill_not_found(self, running_server):
        mock_registry = MagicMock()
        mock_registry.get_skill.side_effect = KeyError("nope")

        with patch("alive.core.skill_registry.get_registry", return_value=mock_registry), \
             patch("alive.network.conversation_log.log_event"):
            resp = httpx.get(f"http://127.0.0.1:{running_server}/alp/skill/nonexistent")

        assert resp.status_code == 404
        assert "not found" in resp.json()["error"]


class TestNotFoundEndpoint:
    def test_unknown_path(self, running_server):
        resp = httpx.get(f"http://127.0.0.1:{running_server}/unknown")
        assert resp.status_code == 404


class TestStartServer:
    def test_start_and_respond(self):
        from alive.network.server import start_server

        port = _find_free_port()
        with patch("alive.network.server.log_event"):
            server, thread = start_server(port)

        try:
            time.sleep(0.1)
            mock_card = {"agent_id": "alp-x", "skills": [], "skill_count": 0}
            with patch("alive.network.identity.get_agent_card", return_value=mock_card):
                resp = httpx.get(f"http://127.0.0.1:{port}/alp/card")
            assert resp.status_code == 200
        finally:
            server.shutdown()
