"""Tests for pyproject.toml-based dependency sync.

Covers:
- _is_uv_project() — uv detection via uv.lock
- _pyproject_sync() — rsync workspace + install on remote
- sync_deps() — pyproject.toml detection + routing
- Educational warning when no pyproject.toml found
"""

from __future__ import annotations

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

import pytest

from specter_cli.deps import (
    _is_uv_project,
    _pyproject_sync,
    sync_deps,
)

_SUBPROCESS_RUN = "specter_cli.deps.subprocess.run"
_RSYNC_SSH = "specter_cli.deps.rsync_ssh_args"
_SSH_BASE = "specter_cli.deps.ssh_base_args"
_RSYNC_WORKSPACE = "specter_cli.sync.rsync_workspace"
_FIND_ROOT = "specter_cli.sync.find_project_root"


@pytest.fixture
def fake_venv(tmp_path: Path) -> Path:
    """Create a minimal fake venv (kept for sync_deps signature compat)."""
    venv = tmp_path / "venv"
    venv.mkdir()
    return venv


class TestIsUvProject:
    """_is_uv_project detects uv via uv.lock."""

    def test_true_when_uv_lock_exists(self, tmp_path: Path) -> None:
        (tmp_path / "uv.lock").write_text("")
        assert _is_uv_project(tmp_path) is True

    def test_false_when_no_uv_lock(self, tmp_path: Path) -> None:
        assert _is_uv_project(tmp_path) is False

    def test_false_when_uv_lock_is_directory(self, tmp_path: Path) -> None:
        (tmp_path / "uv.lock").mkdir()
        assert _is_uv_project(tmp_path) is False


class TestPyprojectSync:
    """_pyproject_sync rsyncs workspace and installs deps on remote."""

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    def test_uv_pip_install_for_standard_project(
        self,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
    ) -> None:
        """Standard project (no uv.lock) → uv pip install . on remote."""
        (tmp_path / "pyproject.toml").write_text("[project]\nname='test'\n")

        mock_run.return_value = MagicMock(returncode=0, stderr="")

        result = _pyproject_sync(tmp_path, host="1.2.3.4", user="root")

        assert result is True
        # Workspace was rsynced.
        mock_rsync.assert_called_once()
        # SSH uv pip install . was called.
        ssh_cmd = mock_run.call_args[0][0]
        cmd_str = " ".join(ssh_cmd)
        assert "uv pip install" in cmd_str
        assert "." in cmd_str

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    def test_uv_sync_for_uv_project(
        self,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
    ) -> None:
        """uv project (uv.lock present) → uv sync --frozen on remote."""
        (tmp_path / "pyproject.toml").write_text("[project]\nname='test'\n")
        (tmp_path / "uv.lock").write_text("")

        mock_run.return_value = MagicMock(returncode=0, stderr="")

        result = _pyproject_sync(tmp_path, host="1.2.3.4", user="root")

        assert result is True
        mock_rsync.assert_called_once()
        ssh_cmd = mock_run.call_args[0][0]
        cmd_str = " ".join(ssh_cmd)
        assert "uv sync --frozen" in cmd_str
        assert "UV_PROJECT_ENVIRONMENT" in cmd_str

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    def test_uv_failure_is_nonfatal(
        self,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
    ) -> None:
        """Remote uv install failure is non-fatal — returns True, logs warning."""
        (tmp_path / "pyproject.toml").write_text("[project]\nname='test'\n")

        mock_run.return_value = MagicMock(
            returncode=1, stderr="ERROR: Could not find package"
        )

        result = _pyproject_sync(tmp_path, host="1.2.3.4", user="root")

        # Still True — we attempted the sync.
        assert result is True

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    def test_uv_not_found_shows_clear_message(
        self,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Missing uv on remote → clear user-facing error with workaround."""
        (tmp_path / "pyproject.toml").write_text("[project]\nname='test'\n")

        mock_run.return_value = MagicMock(
            returncode=127, stderr="bash: uv: not found"
        )

        result = _pyproject_sync(tmp_path, host="1.2.3.4", user="root")

        assert result is True
        stderr = capsys.readouterr().err
        assert "uv not available on remote" in stderr
        assert "pip install ." in stderr

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    def test_rsync_failure_propagates(
        self,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
    ) -> None:
        """rsync failure should propagate as an exception."""
        import subprocess

        (tmp_path / "pyproject.toml").write_text("[project]\nname='test'\n")
        mock_rsync.side_effect = subprocess.CalledProcessError(
            1, ["rsync"], stderr="Connection refused"
        )

        with pytest.raises(subprocess.CalledProcessError):
            _pyproject_sync(tmp_path, host="1.2.3.4", user="root")

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    def test_custom_port_and_key(
        self,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        mock_ssh_base: MagicMock,
        tmp_path: Path,
    ) -> None:
        """SSH connection params are forwarded correctly."""
        (tmp_path / "pyproject.toml").write_text("[project]\nname='test'\n")
        mock_run.return_value = MagicMock(returncode=0, stderr="")

        _pyproject_sync(
            tmp_path,
            host="5.6.7.8",
            user="ubuntu",
            port=2222,
            key_path="/tmp/key",
        )

        mock_ssh_base.assert_called_once_with(
            "5.6.7.8", "ubuntu", port=2222, key_path="/tmp/key"
        )


class TestSyncDeps:
    """sync_deps routes to _pyproject_sync or returns False."""

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    @patch(_FIND_ROOT)
    def test_pyproject_found_runs_sync(
        self,
        mock_find_root: MagicMock,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
        fake_venv: Path,
    ) -> None:
        """pyproject.toml present → sync runs, returns True."""
        project = tmp_path / "project"
        project.mkdir()
        (project / "pyproject.toml").write_text("[project]\nname='test'\n")
        mock_find_root.return_value = project

        mock_run.return_value = MagicMock(returncode=0, stderr="")

        result = sync_deps(fake_venv, host="1.2.3.4", user="root")

        assert result is True
        mock_rsync.assert_called_once()
        mock_run.assert_called_once()

    @patch(_SUBPROCESS_RUN)
    @patch(_FIND_ROOT)
    def test_no_pyproject_returns_false(
        self,
        mock_find_root: MagicMock,
        mock_run: MagicMock,
        tmp_path: Path,
        fake_venv: Path,
    ) -> None:
        """No pyproject.toml → returns False, no SSH calls."""
        project = tmp_path / "project"
        project.mkdir()
        mock_find_root.return_value = project

        result = sync_deps(fake_venv, host="1.2.3.4", user="root")

        assert result is False
        mock_run.assert_not_called()

    @patch(_FIND_ROOT)
    def test_no_pyproject_prints_warning(
        self,
        mock_find_root: MagicMock,
        tmp_path: Path,
        fake_venv: Path,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """No pyproject.toml → prints educational warning to stderr."""
        project = tmp_path / "project"
        project.mkdir()
        mock_find_root.return_value = project

        sync_deps(fake_venv, host="1.2.3.4", user="root")

        stderr = capsys.readouterr().err
        assert "pyproject.toml" in stderr
        assert "uv init" in stderr or "hatch init" in stderr

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    @patch(_FIND_ROOT)
    def test_uv_project_uses_uv_sync(
        self,
        mock_find_root: MagicMock,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
        fake_venv: Path,
    ) -> None:
        """uv project (uv.lock) → uv sync --frozen on remote."""
        project = tmp_path / "project"
        project.mkdir()
        (project / "pyproject.toml").write_text("[project]\nname='test'\n")
        (project / "uv.lock").write_text("")
        mock_find_root.return_value = project

        mock_run.return_value = MagicMock(returncode=0, stderr="")

        result = sync_deps(fake_venv, host="1.2.3.4", user="root")

        assert result is True
        ssh_cmd = mock_run.call_args[0][0]
        cmd_str = " ".join(ssh_cmd)
        assert "uv sync --frozen" in cmd_str

    @patch(_SSH_BASE, return_value=["-o", "StrictHostKeyChecking=no", "root@1.2.3.4"])
    @patch(_RSYNC_WORKSPACE)
    @patch(_SUBPROCESS_RUN)
    @patch(_FIND_ROOT)
    def test_standard_project_uses_uv_pip(
        self,
        mock_find_root: MagicMock,
        mock_run: MagicMock,
        mock_rsync: MagicMock,
        _ssh_base: MagicMock,
        tmp_path: Path,
        fake_venv: Path,
    ) -> None:
        """Standard project (no uv.lock) → uv pip install . on remote."""
        project = tmp_path / "project"
        project.mkdir()
        (project / "pyproject.toml").write_text("[project]\nname='test'\n")
        mock_find_root.return_value = project

        mock_run.return_value = MagicMock(returncode=0, stderr="")

        result = sync_deps(fake_venv, host="1.2.3.4", user="root")

        assert result is True
        ssh_cmd = mock_run.call_args[0][0]
        cmd_str = " ".join(ssh_cmd)
        assert "uv pip install" in cmd_str
