"""Tests for llamafile provisioning and infra path detection."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

# ---------------------------------------------------------------------------
# llamafile module tests
# ---------------------------------------------------------------------------


class TestGetLlamafileDir:
    def test_creates_directory(self, tmp_path):
        with patch("axiom.setup.llamafile.Path") as mock_path:
            mock_dir = MagicMock()
            mock_path.home.return_value.__truediv__ = MagicMock(return_value=mock_dir)
            mock_dir.__truediv__ = MagicMock(return_value=mock_dir)
            mock_dir.mkdir = MagicMock()

            from axiom.setup.llamafile import get_llamafile_dir

            # Reset the mock to use tmp_path for a real test
        with patch("axiom.setup.llamafile.Path.home", return_value=tmp_path):
            from axiom.setup.llamafile import get_llamafile_dir

            result = get_llamafile_dir()
            assert result.exists()
            assert result == tmp_path / ".axi" / "llamafile"


class TestIsLlamafileInstalled:
    def test_returns_false_when_not_downloaded(self, tmp_path):
        with patch("axiom.setup.llamafile.get_llamafile_dir", return_value=tmp_path):
            from axiom.setup.llamafile import is_llamafile_installed

            assert is_llamafile_installed() is False

    def test_returns_true_when_both_exist(self, tmp_path):
        (tmp_path / "llamafile").touch()
        (tmp_path / "Bonsai-1.7B.gguf").touch()

        with patch("axiom.setup.llamafile.get_llamafile_dir", return_value=tmp_path):
            from axiom.setup.llamafile import is_llamafile_installed

            assert is_llamafile_installed() is True

    def test_returns_false_when_only_binary(self, tmp_path):
        (tmp_path / "llamafile").touch()

        with patch("axiom.setup.llamafile.get_llamafile_dir", return_value=tmp_path):
            from axiom.setup.llamafile import is_llamafile_installed

            assert is_llamafile_installed() is False


class TestGetStatus:
    def test_returns_correct_dict(self, tmp_path):
        with (
            patch("axiom.setup.llamafile.get_llamafile_dir", return_value=tmp_path),
            patch("axiom.setup.llamafile.is_llamafile_installed", return_value=False),
            patch("axiom.setup.llamafile.is_llamafile_running", return_value=False),
        ):
            from axiom.setup.llamafile import get_status

            status = get_status()
            assert status["installed"] is False
            assert status["running"] is False
            assert status["port"] == 8080
            assert status["model"] == "Bonsai-1.7B.gguf"
            assert status["path"] == str(tmp_path)


class TestIsLlamafileRunning:
    def test_returns_false_when_not_running(self):
        from axiom.setup.llamafile import is_llamafile_running

        # Use a port that's almost certainly not in use
        assert is_llamafile_running(port=19999) is False

    def test_returns_true_when_connectable(self):
        with patch("axiom.setup.llamafile.socket.create_connection") as mock_conn:
            mock_conn.return_value.__enter__ = MagicMock()
            mock_conn.return_value.__exit__ = MagicMock(return_value=False)

            from axiom.setup.llamafile import is_llamafile_running

            assert is_llamafile_running() is True


class TestStopLlamafile:
    def test_returns_false_when_no_pid_file(self, tmp_path):
        with patch("axiom.setup.llamafile.get_llamafile_dir", return_value=tmp_path):
            from axiom.setup.llamafile import stop_llamafile

            assert stop_llamafile() is False

    def test_kills_process_and_removes_pid(self, tmp_path):
        pid_file = tmp_path / "llamafile.pid"
        pid_file.write_text("12345")

        with (
            patch("axiom.setup.llamafile.get_llamafile_dir", return_value=tmp_path),
            patch("os.kill") as mock_kill,
        ):
            from axiom.setup.llamafile import stop_llamafile

            assert stop_llamafile() is True
            mock_kill.assert_called_once_with(12345, 15)
            assert not pid_file.exists()


# ---------------------------------------------------------------------------
# Infra path detection tests
# ---------------------------------------------------------------------------


class TestDetectInfraPath:
    def test_returns_native_when_no_docker_or_k3d(self):
        with patch("axiom.setup.infra.shutil.which", return_value=None):
            from axiom.setup.infra import detect_infra_path

            assert detect_infra_path() == "native"

    def test_returns_k3d_when_both_available(self):
        def fake_which(name):
            return f"/usr/bin/{name}" if name in ("k3d", "docker") else None

        with patch("axiom.setup.infra.shutil.which", side_effect=fake_which):
            from axiom.setup.infra import detect_infra_path

            assert detect_infra_path() == "k3d"

    def test_returns_docker_compose_when_docker_but_no_k3d(self):
        def fake_which(name):
            return "/usr/bin/docker" if name == "docker" else None

        mock_result = MagicMock()
        mock_result.returncode = 0

        with (
            patch("axiom.setup.infra.shutil.which", side_effect=fake_which),
            patch("axiom.setup.infra.subprocess.run", return_value=mock_result),
        ):
            from axiom.setup.infra import detect_infra_path

            assert detect_infra_path() == "docker-compose"

    def test_returns_native_when_docker_not_running(self):
        def fake_which(name):
            return "/usr/bin/docker" if name == "docker" else None

        mock_result = MagicMock()
        mock_result.returncode = 1

        with (
            patch("axiom.setup.infra.shutil.which", side_effect=fake_which),
            patch("axiom.setup.infra.subprocess.run", return_value=mock_result),
        ):
            from axiom.setup.infra import detect_infra_path

            assert detect_infra_path() == "native"


# ---------------------------------------------------------------------------
# Native PostgreSQL detection tests
# ---------------------------------------------------------------------------


class TestProvisionPostgresNative:
    def test_detects_running_pg(self):
        with patch("axiom.setup.infra.socket.create_connection") as mock_conn:
            mock_conn.return_value.__enter__ = MagicMock()
            mock_conn.return_value.__exit__ = MagicMock(return_value=False)

            from axiom.setup.infra import provision_postgres_native

            result = provision_postgres_native()
            assert result["running"] is True
            assert result["method"] == "existing"

    def test_suggests_brew_on_macos(self):
        with (
            patch(
                "axiom.setup.infra.socket.create_connection",
                side_effect=ConnectionRefusedError,
            ),
            patch("axiom.setup.infra.shutil.which", return_value="/opt/homebrew/bin/brew"),
        ):
            from axiom.setup.infra import provision_postgres_native

            result = provision_postgres_native()
            assert result["running"] is False
            assert result["method"] == "brew"
            assert any("brew install" in i for i in result["instructions"])

    def test_falls_back_to_manual(self):
        with (
            patch(
                "axiom.setup.infra.socket.create_connection",
                side_effect=ConnectionRefusedError,
            ),
            patch("axiom.setup.infra.shutil.which", return_value=None),
        ):
            from axiom.setup.infra import provision_postgres_native

            result = provision_postgres_native()
            assert result["running"] is False
            assert result["method"] == "manual"


# ---------------------------------------------------------------------------
# Docker Compose provisioning tests
# ---------------------------------------------------------------------------


class TestProvisionPostgresCompose:
    def test_returns_true_when_compose_succeeds(self):
        mock_result = MagicMock()
        mock_result.returncode = 0

        with patch("axiom.setup.infra.subprocess.run", return_value=mock_result), \
             patch("axiom.setup.secrets.get_secret", return_value="test-pw"), \
             patch("axiom.setup.secrets.store_secret", return_value=True):
            from axiom.setup.infra import provision_postgres_compose

            # The real compose file exists in the package — subprocess is mocked
            result = provision_postgres_compose()
            assert result is True

    def test_returns_false_when_compose_fails(self):
        mock_result = MagicMock()
        mock_result.returncode = 1

        with patch("axiom.setup.infra.subprocess.run", return_value=mock_result), \
             patch("axiom.setup.secrets.get_secret", return_value="test-pw"), \
             patch("axiom.setup.secrets.store_secret", return_value=True):
            from axiom.setup.infra import provision_postgres_compose

            result = provision_postgres_compose()
            assert result is False
