"""Tests for system introspection tools."""

from unittest.mock import MagicMock, patch

import httpx


def test_get_system_info_returns_dict():
    """System info should return a dictionary."""
    from timmy.tools_intro import get_system_info

    info = get_system_info()

    assert isinstance(info, dict)
    assert "python_version" in info
    assert "platform" in info
    assert "model" in info
    assert "repo_root" in info


def test_get_system_info_contains_model():
    """System info should include a model name (may differ from config if
    the actual running model is different — see issue #77)."""
    from timmy.tools_intro import get_system_info

    info = get_system_info()

    assert "model" in info
    # Model should be a non-empty string — exact value depends on what
    # Ollama has loaded (verified by TestGetOllamaModelExactMatch tests)
    assert isinstance(info["model"], str)
    assert len(info["model"]) > 0


def test_get_system_info_contains_repo_root():
    """System info should include repo_root."""
    from config import settings
    from timmy.tools_intro import get_system_info

    info = get_system_info()

    assert "repo_root" in info
    assert info["repo_root"] == settings.repo_root
    # In Docker the CWD is /app, so just verify it's a non-empty path
    assert len(info["repo_root"]) > 0


def test_check_ollama_health_returns_dict():
    """Ollama health check should return a dictionary."""
    from timmy.tools_intro import check_ollama_health

    result = check_ollama_health()

    assert isinstance(result, dict)
    assert "accessible" in result
    assert "model" in result


def test_get_memory_status_returns_dict():
    """Memory status should return a dictionary with tier info."""
    from timmy.tools_intro import get_memory_status

    status = get_memory_status()

    assert isinstance(status, dict)
    assert "tier1_hot_memory" in status
    assert "tier2_vault" in status


# --- _get_ollama_model exact-match tests (issue #77) ---


def _mock_response(json_data, status_code=200):
    """Create a mock httpx response."""
    resp = MagicMock(spec=httpx.Response)
    resp.status_code = status_code
    resp.json.return_value = json_data
    return resp


class TestGetOllamaModelExactMatch:
    """Ensure _get_ollama_model uses exact match, not prefix match."""

    @patch("timmy.tools_intro.httpx.get")
    def test_exact_match_from_ps(self, mock_get):
        """Should return exact model from /api/ps."""
        from timmy.tools_intro import _get_ollama_model

        ps_resp = _mock_response({"models": [{"name": "qwen3:30b"}]})
        mock_get.return_value = ps_resp

        with patch("config.settings") as mock_settings:
            mock_settings.ollama_model = "qwen3:30b"
            mock_settings.ollama_url = "http://localhost:11434"
            result = _get_ollama_model()

        assert result == "qwen3:30b"

    @patch("timmy.tools_intro.httpx.get")
    def test_prefix_collision_returns_correct_model(self, mock_get):
        """qwen3:8b configured — must NOT match qwen3:30b (prefix bug)."""
        from timmy.tools_intro import _get_ollama_model

        # /api/ps has both models loaded; configured is qwen3:8b
        ps_resp = _mock_response({"models": [{"name": "qwen3:30b"}, {"name": "qwen3:8b"}]})
        mock_get.return_value = ps_resp

        with patch("config.settings") as mock_settings:
            mock_settings.ollama_model = "qwen3:8b"
            mock_settings.ollama_url = "http://localhost:11434"
            result = _get_ollama_model()

        assert result == "qwen3:8b", f"Got '{result}' — prefix collision bug!"

    @patch("timmy.tools_intro.httpx.get")
    def test_configured_model_not_running_returns_actual(self, mock_get):
        """If configured model isn't loaded, report what IS running."""
        from timmy.tools_intro import _get_ollama_model

        ps_resp = _mock_response({"models": [{"name": "qwen3:30b"}]})
        mock_get.return_value = ps_resp

        with patch("config.settings") as mock_settings:
            mock_settings.ollama_model = "qwen3:8b"
            mock_settings.ollama_url = "http://localhost:11434"
            result = _get_ollama_model()

        # Should report actual running model, not configured one
        assert result == "qwen3:30b"

    @patch("timmy.tools_intro.httpx.get")
    def test_latest_suffix_match(self, mock_get):
        """'qwen3:30b' config should match 'qwen3:30b:latest' from API."""
        from timmy.tools_intro import _get_ollama_model

        ps_resp = _mock_response({"models": []})
        tags_resp = _mock_response({"models": [{"name": "qwen3:30b:latest"}]})
        mock_get.side_effect = [ps_resp, tags_resp]

        with patch("config.settings") as mock_settings:
            mock_settings.ollama_model = "qwen3:30b"
            mock_settings.ollama_url = "http://localhost:11434"
            result = _get_ollama_model()

        # Falls back to configured since no exact match
        assert result == "qwen3:30b"

    @patch("timmy.tools_intro.httpx.get")
    def test_ollama_down_returns_configured(self, mock_get):
        """If Ollama is unreachable, return configured model."""
        from timmy.tools_intro import _get_ollama_model

        mock_get.side_effect = httpx.ConnectError("connection refused")

        with patch("config.settings") as mock_settings:
            mock_settings.ollama_model = "qwen3:30b"
            mock_settings.ollama_url = "http://localhost:11434"
            result = _get_ollama_model()

        assert result == "qwen3:30b"


class TestRunSelfTests:
    """Tests for run_self_tests() — Timmy's self-verification tool."""

    def test_returns_dict_with_expected_keys(self, monkeypatch, tmp_path):
        """run_self_tests should return structured test results."""
        import subprocess

        def mock_run(*args, **kwargs):
            return subprocess.CompletedProcess(
                args=args[0] if args else [],
                returncode=0,
                stdout="5 passed in 0.5s",
                stderr="",
            )

        monkeypatch.setattr(subprocess, "run", mock_run)

        # Create fake venv so check passes
        venv_python = tmp_path / ".venv" / "bin" / "python"
        venv_python.parent.mkdir(parents=True)
        venv_python.write_text("#!/bin/sh\necho mock")

        from timmy.tools_intro import run_self_tests

        result = run_self_tests(scope="tests/timmy/test_introspection.py", _repo_root=str(tmp_path))
        assert isinstance(result, dict)
        assert "success" in result
        assert result["success"] is True
        assert "passed" in result
        assert "failed" in result
        assert "total" in result
        assert result["passed"] == 5
        assert result["total"] == 5

    def test_fast_scope_skips_integration(self, monkeypatch, tmp_path):
        """Fast scope should exclude functional/e2e/integration dirs."""
        import subprocess

        calls = []

        def capture_run(*args, **kwargs):
            calls.append(args[0] if args else kwargs.get("cmd"))
            # Return a fake result
            return subprocess.CompletedProcess(
                args=args[0] if args else [], returncode=0, stdout="1 passed in 0.5s", stderr=""
            )

        monkeypatch.setattr(subprocess, "run", capture_run)

        # Create fake venv so check passes
        venv_python = tmp_path / ".venv" / "bin" / "python"
        venv_python.parent.mkdir(parents=True)
        venv_python.write_text("#!/bin/sh\necho mock")

        from timmy.tools_intro import run_self_tests

        run_self_tests(scope="fast", _repo_root=str(tmp_path))
        assert len(calls) == 1
        cmd = calls[0]
        assert "--ignore=tests/functional" in cmd
        assert "--ignore=tests/e2e" in cmd

    def test_specific_path_scope(self, monkeypatch, tmp_path):
        """Specific path scope passes path directly to pytest."""
        import subprocess

        calls = []

        def capture_run(*args, **kwargs):
            calls.append(args[0] if args else kwargs.get("cmd"))
            return subprocess.CompletedProcess(
                args=args[0] if args else [], returncode=0, stdout="5 passed in 1.0s", stderr=""
            )

        monkeypatch.setattr(subprocess, "run", capture_run)

        # Create fake venv so check passes
        venv_python = tmp_path / ".venv" / "bin" / "python"
        venv_python.parent.mkdir(parents=True)
        venv_python.write_text("#!/bin/sh\necho mock")

        from timmy.tools_intro import run_self_tests

        run_self_tests(scope="tests/timmy/", _repo_root=str(tmp_path))
        assert len(calls) == 1
        assert "tests/timmy/" in calls[0]

    def test_missing_venv_returns_error(self, monkeypatch, tmp_path):
        """Should handle missing venv gracefully."""
        from timmy.tools_intro import run_self_tests

        result = run_self_tests(_repo_root=str(tmp_path))
        assert result["success"] is False
        assert "venv" in result.get("error", "").lower()

    def test_timeout_returns_error(self, monkeypatch, tmp_path):
        """Should handle subprocess timeout gracefully."""
        import subprocess

        def timeout_run(*args, **kwargs):
            raise subprocess.TimeoutExpired(cmd="pytest", timeout=120)

        monkeypatch.setattr(subprocess, "run", timeout_run)

        # Create fake venv so check passes
        venv_python = tmp_path / ".venv" / "bin" / "python"
        venv_python.parent.mkdir(parents=True)
        venv_python.write_text("#!/bin/sh\necho mock")

        from timmy.tools_intro import run_self_tests

        result = run_self_tests(_repo_root=str(tmp_path))
        assert result["success"] is False
        assert "timed out" in result.get("error", "").lower()
