"""Tests for change-url command."""

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

import typer
from typer.testing import CliRunner

from repo_sync_kitty.commands.change_url import ChangeResult, _change_repo_url
from repo_sync_kitty.config.loader import ResolvedProject


def _make_project(
    path: str = "test/repo",
    remote_name: str = "vladistan",
    remote_url: str = "ssh://git@github.com/vladistan/",
) -> ResolvedProject:
    return ResolvedProject(
        slug=Path(path).name,
        path=Path(path),
        remote_name=remote_name,
        remote_url=remote_url,
        branch="main",
        status="active",
        parallelism=4,
        timeout=300,
    )


class TestChangeRepoUrl:
    """Tests for _change_repo_url internal function."""

    def test_skips_missing_repo(self, tmp_path: Path) -> None:
        project = _make_project()
        result = _change_repo_url(project, tmp_path, "https")
        assert result.action == "skipped"
        assert "not cloned" in result.message

    def test_skips_no_origin(self, tmp_path: Path) -> None:
        project = _make_project()
        repo_path = tmp_path / project.path
        repo_path.mkdir(parents=True)
        (repo_path / ".git").mkdir()

        with patch("repo_sync_kitty.commands.change_url.RepoManager") as MockMgr:
            mock_mgr = MagicMock()
            mock_mgr.get_remote_urls.return_value = {"upstream": "https://example.com"}
            MockMgr.return_value = mock_mgr

            result = _change_repo_url(project, tmp_path, "https")

        assert result.action == "skipped"
        assert "no origin" in result.message

    def test_skips_already_target_protocol(self, tmp_path: Path) -> None:
        project = _make_project()
        repo_path = tmp_path / project.path
        repo_path.mkdir(parents=True)
        (repo_path / ".git").mkdir()

        with patch("repo_sync_kitty.commands.change_url.RepoManager") as MockMgr:
            mock_mgr = MagicMock()
            mock_mgr.get_remote_urls.return_value = {
                "origin": "https://github.com/vladistan/repo.git"
            }
            MockMgr.return_value = mock_mgr

            result = _change_repo_url(project, tmp_path, "https")

        assert result.action == "skipped"
        assert "already using" in result.message

    def test_changes_ssh_to_https(self, tmp_path: Path) -> None:
        project = _make_project()
        repo_path = tmp_path / project.path
        repo_path.mkdir(parents=True)
        (repo_path / ".git").mkdir()

        with patch("repo_sync_kitty.commands.change_url.RepoManager") as MockMgr:
            mock_mgr = MagicMock()
            mock_mgr.get_remote_urls.return_value = {
                "origin": "ssh://git@github.com/vladistan/repo.git"
            }
            MockMgr.return_value = mock_mgr

            result = _change_repo_url(project, tmp_path, "https")

        assert result.action == "changed"
        assert result.old_url == "ssh://git@github.com/vladistan/repo.git"
        assert result.new_url == "https://github.com/vladistan/repo.git"
        mock_mgr.set_remote_url.assert_called_once_with(
            "origin", "https://github.com/vladistan/repo.git"
        )

    def test_changes_https_to_ssh(self, tmp_path: Path) -> None:
        project = _make_project()
        repo_path = tmp_path / project.path
        repo_path.mkdir(parents=True)
        (repo_path / ".git").mkdir()

        with patch("repo_sync_kitty.commands.change_url.RepoManager") as MockMgr:
            mock_mgr = MagicMock()
            mock_mgr.get_remote_urls.return_value = {
                "origin": "https://github.com/vladistan/repo.git"
            }
            MockMgr.return_value = mock_mgr

            result = _change_repo_url(project, tmp_path, "ssh")

        assert result.action == "changed"
        assert result.new_url == "ssh://git@github.com/vladistan/repo.git"
        mock_mgr.set_remote_url.assert_called_once_with(
            "origin", "ssh://git@github.com/vladistan/repo.git"
        )

    def test_dry_run_does_not_modify(self, tmp_path: Path) -> None:
        project = _make_project()
        repo_path = tmp_path / project.path
        repo_path.mkdir(parents=True)
        (repo_path / ".git").mkdir()

        with patch("repo_sync_kitty.commands.change_url.RepoManager") as MockMgr:
            mock_mgr = MagicMock()
            mock_mgr.get_remote_urls.return_value = {
                "origin": "ssh://git@github.com/vladistan/repo.git"
            }
            MockMgr.return_value = mock_mgr

            result = _change_repo_url(project, tmp_path, "https", dry_run=True)

        assert result.action == "changed"
        assert "dry run" in result.message
        mock_mgr.set_remote_url.assert_not_called()


class TestChangeResult:
    """Tests for ChangeResult dataclass."""

    def test_changed_result(self) -> None:
        project = _make_project()
        result = ChangeResult(
            project=project,
            action="changed",
            old_url="ssh://git@github.com/o/r.git",
            new_url="https://github.com/o/r.git",
        )
        assert result.action == "changed"
        assert result.old_url != result.new_url

    def test_skipped_result(self) -> None:
        project = _make_project()
        result = ChangeResult(
            project=project, action="skipped", message="not cloned"
        )
        assert result.action == "skipped"


class TestChangeUrlValidation:
    """Tests for change_url CLI validation."""

    def test_requires_remote_or_path(self) -> None:
        from repo_sync_kitty.commands.change_url import change_url

        app = typer.Typer()
        app.command()(change_url)
        runner = CliRunner()

        result = runner.invoke(app, ["--protocol", "https"])
        assert result.exit_code != 0
        assert "Must specify --remote or a repo path" in result.output
