"""
Tests for pulse.api — critical path coverage.
Tests the routers directly (no FastAPI app startup needed — avoids
loading pulse_coordinator which requires a running brain).
Targets: pulse_api_brain._read_md_items, brain endpoints,
         pulse_api_system._is_process_running, _load_json, system endpoints.
"""
from __future__ import annotations

import json
import sys
import os
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest

PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

# ---------------------------------------------------------------------------
# pulse_api_brain — unit tests for helpers and endpoints
# ---------------------------------------------------------------------------

class TestReadMdItems:
    def setup_method(self):
        from pulse.api.pulse_api_brain import _read_md_items
        self._read = _read_md_items

    def test_returns_empty_for_missing_file(self, tmp_path):
        result = self._read(tmp_path / "missing.md")
        assert result == []

    def test_skips_header_lines(self, tmp_path):
        f = tmp_path / "doc.md"
        f.write_text("# Header\n## Sub\nactual content\n", encoding="utf-8")
        result = self._read(f)
        assert result == ["actual content"]

    def test_skips_empty_lines(self, tmp_path):
        f = tmp_path / "doc.md"
        f.write_text("line one\n\n\nline two\n", encoding="utf-8")
        result = self._read(f)
        assert result == ["line one", "line two"]

    def test_respects_max_items(self, tmp_path):
        f = tmp_path / "doc.md"
        f.write_text("\n".join(f"item {i}" for i in range(100)), encoding="utf-8")
        result = self._read(f, max_items=10)
        assert len(result) == 10

    def test_strips_whitespace(self, tmp_path):
        f = tmp_path / "doc.md"
        f.write_text("  padded line  \n", encoding="utf-8")
        result = self._read(f)
        assert result == ["padded line"]

    def test_returns_all_non_header_lines(self, tmp_path):
        f = tmp_path / "doc.md"
        f.write_text("# Title\nfoo\nbar\nbaz\n", encoding="utf-8")
        result = self._read(f)
        assert result == ["foo", "bar", "baz"]


class TestBrainEndpointsAsync:
    """Test brain endpoint functions directly (async, no HTTP stack)."""

    @pytest.mark.asyncio
    async def test_get_lessons_missing_file(self):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            m.HIPPOCAMPUS = Path("/nonexistent/path/that/does/not/exist")
            result = await m.get_lessons()
            assert result["count"] == 0
            assert result["lessons"] == []
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_get_lessons_with_data(self, tmp_path):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            lessons_dir = tmp_path / "Lessons"
            lessons_dir.mkdir()
            (lessons_dir / "auto_lessons.md").write_text(
                "# Lessons\nLesson one\nLesson two\n", encoding="utf-8"
            )
            m.HIPPOCAMPUS = tmp_path
            result = await m.get_lessons()
            assert result["count"] == 2
            assert "Lesson one" in result["lessons"]
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_get_failures_missing_file(self):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            m.HIPPOCAMPUS = Path("/nonexistent/xyz")
            result = await m.get_failures()
            assert result["count"] == 0
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_get_patterns_missing_file(self):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            m.HIPPOCAMPUS = Path("/nonexistent/xyz")
            result = await m.get_patterns()
            assert result["count"] == 0
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_get_graph_missing_file(self):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            m.HIPPOCAMPUS = Path("/nonexistent/xyz")
            result = await m.get_graph()
            assert result["nodes"] == 0
            assert result["edges"] == 0
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_get_graph_with_data(self, tmp_path):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            knowledge_dir = tmp_path / "Knowledge"
            knowledge_dir.mkdir(parents=True)
            graph_data = {
                "nodes": [{"id": "n1"}, {"id": "n2"}],
                "edges": [{"source": "n1", "target": "n2"}],
            }
            (knowledge_dir / "knowledge_graph.json").write_text(
                json.dumps(graph_data), encoding="utf-8"
            )
            m.HIPPOCAMPUS = tmp_path
            result = await m.get_graph()
            assert result["nodes"] == 2
            assert result["edges"] == 1
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_get_procedures_missing_file(self):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            m.HIPPOCAMPUS = Path("/nonexistent/xyz")
            result = await m.get_procedures()
            assert result["count"] == 0
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_get_schemas_missing_file(self):
        from pulse.api import pulse_api_brain as m
        orig = m.HIPPOCAMPUS
        try:
            m.HIPPOCAMPUS = Path("/nonexistent/xyz")
            result = await m.get_schemas()
            assert result["count"] == 0
        finally:
            m.HIPPOCAMPUS = orig

    @pytest.mark.asyncio
    async def test_search_brain_short_query_raises(self):
        from pulse.api import pulse_api_brain as m
        from fastapi import HTTPException
        with pytest.raises(HTTPException) as exc_info:
            await m.search_brain(q="x")
        assert exc_info.value.status_code == 400

    @pytest.mark.asyncio
    async def test_search_brain_empty_query_raises(self):
        from pulse.api import pulse_api_brain as m
        from fastapi import HTTPException
        with pytest.raises(HTTPException):
            await m.search_brain(q="")


# ---------------------------------------------------------------------------
# pulse_api_system — unit tests for helpers and endpoints
# ---------------------------------------------------------------------------

class TestIsProcessRunning:
    def setup_method(self):
        from pulse.api.pulse_api_system import _is_process_running
        self._fn = _is_process_running

    def test_zero_pid_returns_false(self):
        assert self._fn(0) is False

    def test_none_pid_returns_false(self):
        assert self._fn(None) is False

    def test_current_process_returns_true(self):
        # Our own PID is definitely running
        assert self._fn(os.getpid()) is True

    def test_nonexistent_pid_returns_false(self):
        # PID 99999999 almost certainly does not exist
        assert self._fn(99999999) is False


class TestSystemLoadJson:
    def setup_method(self):
        from pulse.api.pulse_api_system import _load_json
        self._fn = _load_json

    def test_missing_file_returns_empty_dict(self, tmp_path):
        result = self._fn(tmp_path / "nope.json")
        assert result == {}

    def test_loads_valid_json(self, tmp_path):
        f = tmp_path / "data.json"
        f.write_text(json.dumps({"key": "val"}), encoding="utf-8")
        assert self._fn(f) == {"key": "val"}

    def test_corrupt_json_returns_empty_dict(self, tmp_path):
        f = tmp_path / "bad.json"
        f.write_text("{{{broken", encoding="utf-8")
        assert self._fn(f) == {}


class TestSystemEndpointsAsync:
    """Test system endpoint functions directly (async, no HTTP stack)."""

    @pytest.mark.asyncio
    async def test_get_system_overview_returns_expected_keys(self):
        from pulse.api import pulse_api_system as m
        result = await m.get_system_overview()
        assert "orchestrator" in result
        assert "daemons" in result
        assert "workers" in result
        assert "task_board" in result

    @pytest.mark.asyncio
    async def test_get_system_overview_orchestrator_field(self):
        from pulse.api import pulse_api_system as m
        result = await m.get_system_overview()
        orch = result["orchestrator"]
        assert "running" in orch

    @pytest.mark.asyncio
    async def test_get_system_overview_daemons_field(self):
        from pulse.api import pulse_api_system as m
        result = await m.get_system_overview()
        daemons = result["daemons"]
        assert "alive" in daemons
        assert "dead" in daemons

    @pytest.mark.asyncio
    async def test_get_system_overview_task_board_field(self):
        from pulse.api import pulse_api_system as m
        result = await m.get_system_overview()
        tb = result["task_board"]
        assert "open" in tb or "total" in tb or isinstance(tb, dict)


# ---------------------------------------------------------------------------
# Router registration sanity checks
# ---------------------------------------------------------------------------

class TestTaskBoardAutoClaim:
    """Test dashboard fast tier auto-claim verification."""

    @pytest.mark.asyncio
    async def test_get_tasks_returns_task_board_structure(self):
        """Verify get_tasks endpoint returns correct structure."""
        from pulse.api.pulse_api import app
        from unittest.mock import MagicMock

        # Mock request object
        request = MagicMock()
        request.client.host = "127.0.0.1"

        # Import and test the endpoint function directly
        import pulse.api.pulse_api as api_module
        result = await api_module.get_tasks(request)

        assert result["status"] == "ok"
        assert "dispatches" in result

    def test_task_board_distinguishes_tier_claims(self):
        """Verify task board shows fast-tier claimed and premium-tier open."""
        from pulse.core.brain_utils import load_json_dict, TASK_BOARD_FILE

        # Load actual task board
        board = load_json_dict(TASK_BOARD_FILE)

        # Find the test dispatch (newest one with our tasks)
        test_dispatch = None
        for dispatch in board.get("dispatches", []):
            if "Dashboard test task" in dispatch.get("prompt", ""):
                test_dispatch = dispatch
                break

        if test_dispatch:
            tasks = test_dispatch.get("tasks", [])

            # Find fast and premium tier tasks
            fast_task = next((t for t in tasks if t.get("recommended_tier") == "fast"), None)
            premium_task = next((t for t in tasks if t.get("recommended_tier") == "premium"), None)

            # Fast-tier task should be claimed
            if fast_task:
                assert fast_task.get("status") in ["claimed", "active"], \
                    f"Fast-tier task should be claimed/active, got {fast_task.get('status')}"
                assert fast_task.get("claimed_by") is not None, \
                    "Fast-tier task should have claimed_by value"

            # Premium-tier task should remain open
            if premium_task:
                assert premium_task.get("status") == "open", \
                    f"Premium-tier task should stay open, got {premium_task.get('status')}"
                assert premium_task.get("claimed_by") is None, \
                    "Premium-tier task should not be claimed"


class TestRouterRegistration:
    def test_brain_router_has_prefix(self):
        from pulse.api.pulse_api_brain import router
        assert router.prefix == "/v1/brain"

    def test_system_router_has_prefix(self):
        from pulse.api.pulse_api_system import router
        assert router.prefix == "/v1/system"

    def test_brain_router_has_routes(self):
        from pulse.api.pulse_api_brain import router
        paths = [r.path for r in router.routes]
        assert any("lessons" in p for p in paths)
        assert any("failures" in p for p in paths)
        assert any("patterns" in p for p in paths)
        assert any("graph" in p for p in paths)

    def test_system_router_has_overview(self):
        from pulse.api.pulse_api_system import router
        paths = [r.path for r in router.routes]
        assert any("overview" in p for p in paths)
