"""Tests for condasetup module."""

from __future__ import annotations

import subprocess
from unittest.mock import MagicMock, patch

import pytest

from ..cli import _CONDA_MIRROR_URLS, set_conda_mirror


class TestCondaMirrorURLs:
    """Tests for conda mirror URL configuration."""

    def test_mirror_urls_structure(self):
        """Test that mirror URLs dictionary has correct structure."""
        # Check all expected mirrors are present
        expected_mirrors = {"tsinghua", "ustc", "bsfu", "aliyun"}
        assert set(_CONDA_MIRROR_URLS.keys()) == expected_mirrors

        # Check each mirror has the expected number of URLs
        for _mirror_name, urls in _CONDA_MIRROR_URLS.items():
            assert isinstance(urls, frozenset)
            assert len(urls) >= 6  # Minimum expected URLs

    def test_url_formats(self):
        """Test that all URLs have correct format."""
        for _mirror_name, urls in _CONDA_MIRROR_URLS.items():
            for url in urls:
                # URLs should be strings
                assert isinstance(url, str)
                # URLs should end with slash
                assert url.endswith("/")
                # URLs should contain expected patterns
                assert "anaconda" in url or "conda" in url


class TestSetCondaMirror:
    """Tests for set_conda_mirror function."""

    @patch("subprocess.run")
    @patch("pytola.dev.condasetup.cli.Path.home")
    def test_valid_mirror_selection(self, mock_home, mock_subprocess):
        """Test setting valid mirror."""
        # Mock successful subprocess calls
        mock_subprocess.return_value = MagicMock()

        # Mock home directory and file operations
        mock_home_path = MagicMock()
        old_config_mock = MagicMock()
        old_config_mock.exists.return_value = False  # No existing config
        bak_config_mock = MagicMock()

        # Set up the path operations
        mock_home_path.__truediv__ = lambda self, other: old_config_mock if other == ".condarc" else bak_config_mock
        mock_home.return_value = mock_home_path

        # Test with each valid mirror
        for mirror in ["tsinghua", "ustc", "bsfu", "aliyun"]:
            set_conda_mirror(mirror)

            # Verify subprocess was called for each URL
            expected_calls = len(_CONDA_MIRROR_URLS[mirror])
            # Add one call for show_channel_urls
            assert mock_subprocess.call_count >= expected_calls + 1

            # Reset mock for next iteration
            mock_subprocess.reset_mock()

    @patch("pytola.dev.condasetup.cli.logger")
    def test_invalid_mirror(self, mock_logger):
        """Test handling of invalid mirror name."""
        set_conda_mirror("invalid_mirror")
        mock_logger.error.assert_called_with("Invalid mirror: invalid_mirror")

    @patch("subprocess.run")
    @patch("pytola.dev.condasetup.cli.Path.home")
    @patch("pytola.dev.condasetup.cli.Path.exists")
    def test_existing_config_backup(self, mock_exists, mock_home, mock_subprocess):
        """Test backup of existing .condarc file."""
        # Mock existing config file
        mock_exists.return_value = True
        mock_home_path = MagicMock()
        old_config_mock = MagicMock()
        old_config_mock.exists.return_value = True
        bak_config_mock = MagicMock()
        bak_config_mock.exists.return_value = False

        # Track rename calls
        rename_calls = []

        def mock_rename(destination):
            rename_calls.append(destination)
            return MagicMock()

        old_config_mock.rename = mock_rename

        # Set up the path operations
        def path_div(self, other):
            if other == ".condarc":
                return old_config_mock
            if other == ".condarc.bak":
                return bak_config_mock
            # For numbered backups
            numbered_bak = MagicMock()
            numbered_bak.exists.return_value = False
            return numbered_bak

        mock_home_path.__truediv__ = path_div
        mock_home.return_value = mock_home_path

        # Mock successful subprocess calls
        mock_subprocess.return_value = MagicMock()

        set_conda_mirror("tsinghua")

        # Verify rename was called to backup existing config
        assert len(rename_calls) == 1

    @patch("subprocess.run")
    @patch("pytola.dev.condasetup.cli.Path.home")
    def test_no_existing_config(self, mock_home, mock_subprocess):
        """Test behavior when no existing config file."""
        # Mock no existing config file
        mock_home_path = MagicMock()
        old_config_mock = MagicMock()
        old_config_mock.exists.return_value = False  # No existing config
        bak_config_mock = MagicMock()

        # Set up the path operations
        mock_home_path.__truediv__ = lambda self, other: old_config_mock if other == ".condarc" else bak_config_mock
        mock_home.return_value = mock_home_path

        # Mock successful subprocess calls
        mock_subprocess.return_value = MagicMock()

        set_conda_mirror("tsinghua")

        # Should not attempt backup
        # Verify subprocess calls were made for adding channels
        assert mock_subprocess.called

    @patch("subprocess.run")
    @patch("pytola.dev.condasetup.cli.Path.home")
    @patch("builtins.open")
    @patch("pytola.dev.condasetup.cli.logger")
    def test_subprocess_failure(self, mock_logger, mock_open, mock_home, mock_subprocess):
        """Test handling of subprocess failures."""
        # Mock home directory
        mock_home_path = MagicMock()
        old_config_mock = MagicMock()
        old_config_mock.exists.return_value = False  # No existing config file
        bak_config_mock = MagicMock()

        # Set up the path operations
        mock_home_path.__truediv__ = lambda self, other: old_config_mock if other == ".condarc" else bak_config_mock
        mock_home.return_value = mock_home_path
        mock_open.return_value.__enter__.return_value = MagicMock()

        # Mock subprocess failure
        mock_subprocess.side_effect = subprocess.CalledProcessError(1, "conda config")

        set_conda_mirror("tsinghua")

        # Should log error and return early
        assert mock_logger.exception.called
        # Should not attempt file operations since no existing config
        mock_open.assert_not_called()

    def test_mirror_url_uniqueness(self):
        """Test that URLs within each mirror are unique."""
        for mirror_name, urls in _CONDA_MIRROR_URLS.items():
            assert len(urls) == len(set(urls)), f"Duplicate URLs found in {mirror_name}"

    def test_cross_mirror_url_difference(self):
        """Test that different mirrors have different URLs."""
        mirrors = list(_CONDA_MIRROR_URLS.values())

        # Compare each pair of mirrors
        for i in range(len(mirrors)):
            for j in range(i + 1, len(mirrors)):
                # Each mirror should have some unique URLs
                assert mirrors[i] != mirrors[j]


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
