"""Unit tests for pyembedinstall module."""

import logging
import tempfile
import zipfile
from pathlib import Path
from unittest.mock import Mock, patch

import pytest
from hypothesis import given
from hypothesis import strategies as st

from ..components import parser as _parser
from ..components.embedinstaller import (
    _DEFAULT_CACHE_DIR,
    ARCH_DICT,
    EmbedDownloader,
    EmbedInstaller,
    parse_args,
)


class TestArchitectureMapping:
    """Test cases for architecture mapping."""

    @pytest.mark.parametrize(
        ("machine_arch", "expected_arch"),
        [
            ("amd64", "amd64"),
            ("x86_64", "amd64"),
            ("x86", "amd64"),
            ("AMD64", "amd64"),
            ("X86_64", "amd64"),
            ("arm64", "arm64"),
            ("aarch64", "arm64"),
            ("ARM64", "arm64"),
            ("AARCH64", "arm64"),
        ],
    )
    def test_valid_arch_mappings(self, machine_arch, expected_arch):
        """Test valid architecture mappings."""
        assert ARCH_DICT.get(machine_arch.lower(), "amd64") == expected_arch

    @pytest.mark.parametrize(
        ("machine_arch", "expected_arch"),
        [
            ("unknown", "amd64"),  # Default fallback
            ("i386", "amd64"),  # Not in dict anymore
            ("i686", "amd64"),  # Not in dict anymore
        ],
    )
    def test_default_arch_fallback(self, machine_arch, expected_arch):
        """Test default architecture fallback."""
        assert ARCH_DICT.get(machine_arch.lower(), "amd64") == expected_arch


@pytest.fixture
def temp_path():
    """Provide temporary path for tests."""
    return Path(tempfile.mkdtemp())


@pytest.fixture
def embed_installer(temp_path):
    """Create a default EmbedInstaller for tests."""
    return EmbedInstaller(
        root_dir=temp_path,
        cache_dir=temp_path,
        offline=False,
        skip_speed_test=True,
    )


@pytest.fixture
def embed_downloader(embed_installer):
    """Create a default EmbedDownloader for tests."""
    return EmbedDownloader(
        parent=embed_installer,
        version="3.8.10",
    )


class TestEmbedDownloaderURLs:
    """Test cases for EmbedDownloader URL generation."""

    def test_get_official_url_template(self, embed_downloader, embed_installer):
        """Test official URL template generation."""
        url = embed_downloader.get_official_url_template()
        assert "python.org" in url
        assert "{version}" in url
        assert embed_installer.arch in url

    def test_get_mirror_url_templates(self, embed_downloader, embed_installer):
        """Test mirror URL templates generation."""
        mirrors = embed_downloader.get_mirror_url_templates()
        assert len(mirrors) >= 3
        assert all("{version}" in url for url in mirrors)
        assert all(embed_installer.arch in url for url in mirrors)

    def test_get_urls_to_test(self, embed_downloader, embed_installer):
        """Test getting URLs to test for speed."""
        urls = embed_downloader.get_urls_to_test()
        assert len(urls) >= 4  # 3 mirrors + 1 official
        assert all("3.8.10" in url for url in urls)

    def test_download_urls_skip_speed_test(self, embed_downloader, embed_installer):
        """Test download URLs when skipping speed test."""
        urls = embed_downloader.download_urls
        assert len(urls) == 1
        assert "python.org" in urls[0]

    @patch("pytola.dev.pypack.components.embedinstaller.time.time")
    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_download_urls_with_speed_test(self, mock_urlopen, mock_time, embed_downloader):
        """Test download URLs with speed test."""
        # Mock successful responses
        mock_time.return_value = 0
        mock_response = Mock()
        mock_response.status = 200
        mock_urlopen.return_value.__enter__.return_value = mock_response

        urls = embed_downloader.download_urls
        assert len(urls) >= 1


class TestEmbedDownloaderVersionHandling:
    """Test cases for version handling in EmbedDownloader."""

    def test_is_version_complete(self, embed_downloader):
        """Test checking if version is complete."""
        assert embed_downloader.is_version_complete("3.8.10")
        assert embed_downloader.is_version_complete("3.8.10.1")
        assert not embed_downloader.is_version_complete("3.8")
        assert not embed_downloader.is_version_complete("3")

    def test_validate_version_format(self, embed_downloader):
        """Test validating version format."""
        assert embed_downloader.validate_version_format("3.8")
        assert embed_downloader.validate_version_format("3.10")
        assert not embed_downloader.validate_version_format("3.8.10")
        assert not embed_downloader.validate_version_format("3")

    @pytest.mark.parametrize(
        ("version", "expected"),
        [
            ("", False),  # Empty string
            (".", True),  # Only dot - splits into 2 empty parts
            ("..", False),  # Multiple dots
            ("3.", True),  # Trailing dot
            (".8", True),  # Leading dot
            ("abc", False),  # Non-numeric - no dot
            (
                "3.8.10",
                False,
            ),  # Three parts (should be False for validate_version_format)
            ("3.8", True),  # Valid two-part version
        ],
    )
    def test_validate_version_format_edge_cases(self, embed_downloader, version, expected):
        """Test version format validation with edge cases."""
        assert embed_downloader.validate_version_format(version) == expected

    @pytest.mark.parametrize(
        ("version", "expected"),
        [
            ("", False),  # Empty string
            ("3", False),  # Single part
            ("3.8", False),  # Two parts
            ("3.8.10", True),  # Three parts (complete)
            ("3.8.10.1", True),  # Four parts (also complete)
            ("3.8.10.1.5", True),  # Five parts (also complete)
        ],
    )
    def test_is_version_complete_edge_cases(self, embed_downloader, version, expected):
        """Test version completeness check with edge cases."""
        assert embed_downloader.is_version_complete(version) == expected

    def test_extract_version_from_filename(self, embed_downloader, embed_installer):
        """Test extracting version from filename."""
        assert (
            embed_downloader.extract_version_from_filename(
                "python-3.8.10-embed-" + embed_installer.arch, embed_installer.arch
            )
            == "3.8.10"
        )
        assert (
            embed_downloader.extract_version_from_filename(
                "python-3.9.0-embed-" + embed_installer.arch, embed_installer.arch
            )
            == "3.9.0"
        )

    @pytest.mark.parametrize(
        ("version", "arch", "cache_files", "expected"),
        [
            ("3.8", "amd64", ["python-3.8.10-embed-amd64.zip"], "3.8.10"),
            ("3.9", "amd64", ["python-3.9.0-embed-amd64.zip"], "3.9.0"),
            (
                "3.8",
                "amd64",
                ["python-3.8.12-embed-amd64.zip", "python-3.8.10-embed-amd64.zip"],
                "3.8.12",
            ),
            ("3.10", "amd64", [], None),
        ],
    )
    def test_get_cached_version(self, version, arch, cache_files, expected, temp_path):
        """Test getting cached version."""
        # Create mock cache files as valid zip files
        for filename in cache_files:
            cache_file = temp_path / filename
            with zipfile.ZipFile(cache_file, "w") as zf:
                zf.writestr("test.txt", "content")

        installer = EmbedInstaller(
            root_dir=temp_path,
            cache_dir=temp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        major, minor = version.split(".")
        assert downloader.get_cached_version(major, minor, arch, temp_path) == expected


class TestEmbedDownloaderCachedFile:
    """Test cases for cached file handling."""

    @pytest.mark.parametrize(
        ("cache_file_content", "expected_valid"),
        [
            (b"", False),  # Empty file is not a valid zip
            (b"not a zip", False),
            (None, False),  # File doesn't exist
        ],
    )
    def test_has_cached_file_invalid(self, cache_file_content, expected_valid, tmp_path):
        """Test cached file validation for invalid files."""
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        if cache_file_content is not None:
            downloader.cache_file.write_bytes(cache_file_content)

        assert downloader.has_cached_file == expected_valid

    def test_has_cached_file_valid_zip(self, embed_downloader):
        """Test cached file validation for valid zip."""
        # Create a valid zip file
        zip_path = embed_downloader.cache_file
        with zipfile.ZipFile(zip_path, "w") as zf:
            zf.writestr("test.txt", "content")

        assert embed_downloader.has_cached_file is True

    def test_has_cached_file_corrupted_zip_cleanup(self, embed_downloader, caplog):
        """Test that corrupted cache file is cleaned up."""
        # Create a corrupted zip file
        embed_downloader.cache_file.write_bytes(b"corrupted content")
        assert embed_downloader.cache_file.exists()

        # Clear cached property to force re-evaluation
        if "has_cached_file" in embed_downloader.__dict__:
            del embed_downloader.__dict__["has_cached_file"]

        # The main assertion - check that the file is deleted and property returns False
        assert embed_downloader.has_cached_file is False
        assert not embed_downloader.cache_file.exists()

        # Additional verification: the file should no longer exist
        assert not embed_downloader.cache_file.exists()


class TestEmbedDownloaderCheckVersionURL:
    """Test cases for checking version URL availability."""

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_check_version_url_success(self, mock_urlopen, embed_downloader):
        """Test successful version URL check."""
        mock_response = Mock()
        mock_response.status = 200
        mock_urlopen.return_value.__enter__.return_value = mock_response

        assert embed_downloader.check_version_url("3.8.10", "amd64", 5) is True

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_check_version_url_404(self, mock_urlopen, tmp_path):
        """Test version URL check with 404."""
        # Create HTTPError with proper headers parameter using http.client.HTTPMessage
        import email.message
        from urllib.error import HTTPError

        headers = email.message.Message()
        http_error = HTTPError("https://example.com", 404, "Not Found", headers, None)
        mock_urlopen.side_effect = http_error

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        assert downloader.check_version_url("3.8.10", "amd64", 5) is False

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_check_version_url_timeout(self, mock_urlopen, embed_downloader):
        """Test version URL check with timeout."""
        from urllib.error import URLError

        mock_urlopen.side_effect = URLError("Timeout")

        assert embed_downloader.check_version_url("3.8.10", "amd64", 5) is False


class TestEmbedDownloaderDownload:
    """Test cases for download functionality."""

    def test_download_offline_no_cache(self, embed_downloader, caplog):
        """Test download in offline mode with no cache."""
        import logging

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.ERROR):
                embed_downloader.parent.offline = True
                assert embed_downloader.download() is False
                # Check if the error message is in any log record
                error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
                assert any("Offline mode" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    def test_download_with_cache(self, embed_downloader, caplog):
        """Test download with valid cache."""
        caplog.set_level(logging.DEBUG)
        # Create a valid zip file in cache
        zip_path = embed_downloader.cache_file
        with zipfile.ZipFile(zip_path, "w") as zf:
            zf.writestr("python.exe", "fake exe")

        embed_downloader.parent.offline = True
        assert embed_downloader.download() is True


class TestEmbedInstaller:
    """Test cases for EmbedInstaller."""

    def test_runtime_dir_creation(self, embed_installer):
        """Test runtime directory creation."""
        runtime_dir = embed_installer.runtime_dir
        assert runtime_dir.exists()
        assert runtime_dir == embed_installer.root_dir / "dist" / "runtime"

    def test_python_exe_path(self, embed_installer):
        """Test python.exe path calculation."""
        assert embed_installer.python_exe_path == embed_installer.root_dir / "dist" / "runtime" / "python.exe"

    def test_extract_package_success(self, temp_path, caplog):
        """Test successful package extraction."""
        import logging

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Create fresh instances inside the test to ensure they pick up caplog
        embed_installer = EmbedInstaller(
            root_dir=temp_path,
            cache_dir=temp_path,
            offline=False,
            skip_speed_test=True,
        )
        embed_downloader = EmbedDownloader(
            parent=embed_installer,
            version="3.8.10",
        )

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.INFO):
                # Create a valid zip file
                cache_file = embed_downloader.cache_file
                cache_file.parent.mkdir(parents=True, exist_ok=True)
                with zipfile.ZipFile(cache_file, "w") as zf:
                    zf.writestr("python.exe", "fake exe")
                    zf.writestr("python38.dll", "fake dll")

                # Execute the function that generates logs
                result = embed_installer.extract_package(embed_downloader)
                assert result is True
                assert embed_installer.python_exe_path.exists()
                # Check if the success message is in any log record
                info_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.INFO]
                assert any("Extracted 2 files" in msg for msg in info_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    def test_extract_package_invalid_zip(self, temp_path, caplog):
        """Test package extraction with invalid zip."""
        import logging

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Set the log level for all loggers before creating instances
        caplog.set_level(logging.ERROR)

        # Create fresh instances inside the test to ensure they pick up caplog
        embed_installer = EmbedInstaller(
            root_dir=temp_path,
            cache_dir=temp_path,
            offline=False,
            skip_speed_test=True,
        )
        embed_downloader = EmbedDownloader(
            parent=embed_installer,
            version="3.8.10",
        )

        # Add the caplog handler to the module's logger
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Create an invalid zip file
            cache_file = embed_downloader.cache_file
            cache_file.parent.mkdir(parents=True, exist_ok=True)
            cache_file.write_bytes(b"not a zip")

            assert embed_installer.extract_package(embed_downloader) is False
            # Check if the error message is in any log record
            error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
            assert any("Invalid zip file" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestDownloadProgress:
    """Test cases for download progress tracking."""

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_download_file_chunks_with_content_length(self, mock_urlopen, embed_downloader, temp_path):
        """Test downloading file chunks with known content length."""
        # Mock response with content length
        mock_response = Mock()
        chunk = b"chunk"
        mock_response.read.side_effect = [chunk, chunk, chunk, chunk, b""]
        total_size = len(chunk) * 4
        mock_urlopen.return_value.__enter__.return_value = mock_response

        dest_path = temp_path / "test.zip"
        downloaded = embed_downloader.download_file_chunks(mock_response, dest_path, total_size)

        assert downloaded == total_size
        assert dest_path.exists()
        assert dest_path.stat().st_size == total_size

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_download_file_chunks_incomplete(self, mock_urlopen, embed_downloader, temp_path):
        """Test downloading incomplete file."""
        from urllib.error import URLError

        # Mock response with incomplete data
        mock_response = Mock()
        mock_response.read.side_effect = [b"chunk", b""]
        total_size = 1000000  # Much larger than actual
        mock_urlopen.return_value.__enter__.return_value = mock_response

        dest_path = temp_path / "test.zip"

        with pytest.raises(URLError):
            embed_downloader.download_file_chunks(mock_response, dest_path, total_size)


class TestVersionResolution:
    """Test cases for version resolution logic."""

    def test_get_latest_patch_version_complete_version(self, tmp_path, caplog):
        """Test get_latest_patch_version with complete version."""
        caplog.set_level(logging.DEBUG)
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.get_latest_patch_version("3.8.10", "amd64", tmp_path)
        assert result == "3.8.10"

    def test_get_latest_patch_version_invalid_format(self, tmp_path, caplog):
        """Test get_latest_patch_version with invalid format."""
        caplog.set_level(logging.DEBUG)
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.get_latest_patch_version("3", "amd64", tmp_path)
        assert result == "3"

    def test_resolve_patch_version_with_cache(self, tmp_path, caplog):
        """Test resolving patch version with cached files."""
        import logging

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.INFO):
                # Create cached versions
                cached_version = "3.8.10"
                cache_file = tmp_path / f"python-{cached_version}-embed-amd64.zip"
                with zipfile.ZipFile(cache_file, "w") as zf:
                    zf.writestr("test.txt", "content")

                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path,
                    offline=False,
                    skip_speed_test=True,
                )
                downloader = EmbedDownloader(
                    parent=installer,
                    version="3.8",
                )
                result = downloader.resolve_patch_version("3.8", "amd64", tmp_path, 5)
                assert result == cached_version
                # Check if the info message is in any log record
                info_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.INFO]
                assert any("Using cached version" in msg for msg in info_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestPropertyBasedTests:
    """Property-based tests for pyembedinstall."""

    @pytest.mark.parametrize("version", ["3.8.10", "3.9.0", "3.10.5", "3.11.0"])
    def test_extract_version_from_filename_property(self, version):
        """Test that version extraction is deterministic."""
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_path = Path(tmp_dir)
            installer = EmbedInstaller(
                root_dir=tmp_path,
                cache_dir=tmp_path,
                offline=False,
                skip_speed_test=True,
            )
            downloader = EmbedDownloader(
                parent=installer,
                version="3.8.10",
            )
            # Test that extracting twice gives the same result
            filename = f"python-{version}-embed-amd64"
            result1 = downloader.extract_version_from_filename(filename, "amd64")
            result2 = downloader.extract_version_from_filename(filename, "amd64")
            assert result1 == result2

    @pytest.mark.parametrize("arch", ["amd64", "arm64"])
    def test_url_templates_consistency(self, arch, tmp_path):
        """Test that URL templates are consistent for different architectures."""
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        official_url = downloader.get_official_url_template(arch)
        mirror_urls = downloader.get_mirror_url_templates(arch)
        assert arch in official_url
        assert all(arch in url for url in mirror_urls)


class TestErrorHandling:
    """Test error handling in pyembedinstall."""

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_download_with_progress_http_error(self, mock_urlopen, tmp_path):
        """Test download progress with HTTP error."""
        # Create HTTPError with proper headers parameter using email.message.Message
        import email.message
        from urllib.error import HTTPError

        headers = email.message.Message()
        http_error = HTTPError("https://example.com/test.zip", 500, "Internal Error", headers, None)
        mock_urlopen.side_effect = http_error

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        dest_path = tmp_path / "test.zip"
        with pytest.raises(HTTPError):
            downloader.download_with_progress("https://example.com/test.zip", dest_path)

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_download_with_progress_url_error(self, mock_urlopen, tmp_path):
        """Test download progress with URL error."""
        from urllib.error import URLError

        mock_urlopen.side_effect = URLError("Connection failed")

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        dest_path = tmp_path / "test.zip"
        with pytest.raises(URLError):
            downloader.download_with_progress("https://example.com/test.zip", dest_path)

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        dest_path = tmp_path / "test.zip"
        with pytest.raises(URLError):
            downloader.download_with_progress("https://example.com/test.zip", dest_path)


class TestCacheFile:
    """Test cache file path calculation."""

    def test_cache_file_path(self, tmp_path):
        """Test cache file path is correctly calculated."""
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        expected_path = tmp_path / "python-3.8.10-embed-amd64.zip"
        assert downloader.cache_file == expected_path

    def test_cache_file_path_arm64(self, tmp_path):
        """Test cache file path for arm64 architecture."""
        # Note: This test creates an installer, but the actual architecture
        # depends on the host machine's architecture.
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.9.0",
        )
        # The actual architecture depends on the host machine
        expected_path = tmp_path / f"python-3.9.0-embed-{installer.arch}.zip"
        assert downloader.cache_file == expected_path


class TestDefaultCacheDir:
    """Test default cache directory."""

    def test_default_cache_dir_is_absolute_path(self):
        """Test that default cache dir is an absolute path."""
        assert _DEFAULT_CACHE_DIR.is_absolute()

    def test_default_cache_dir_structure(self):
        """Test default cache directory structure."""
        assert ".pytola" in str(_DEFAULT_CACHE_DIR)
        assert ".cache" in str(_DEFAULT_CACHE_DIR)
        assert "embed-python" in str(_DEFAULT_CACHE_DIR)


class TestUrlSpeedTest:
    """Test URL speed testing functionality."""

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_head_request_success(self, mock_urlopen, tmp_path):
        """Test URL speed test with successful HEAD request."""
        mock_response = Mock()
        mock_response.status = 200
        mock_urlopen.return_value.__enter__.return_value = mock_response

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5)
        assert result >= 0  # Time should be positive

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_head_request_not_allowed_fallback(self, mock_urlopen, tmp_path):
        """Test URL speed test falling back to GET when HEAD is not allowed."""
        import email.message
        from urllib.error import HTTPError

        # First call raises HTTP 405 (Method Not Allowed), second succeeds
        headers = email.message.Message()
        http_error = HTTPError("https://example.com", 405, "Method Not Allowed", headers, None)
        mock_urlopen.side_effect = [http_error]

        # Need to mock the GET request separately since it will be called with different parameters
        with patch("pytola.dev.pypack.components.embedinstaller.urlopen") as mock_get_urlopen:
            mock_response = Mock()
            mock_response.status = 200
            mock_get_urlopen.return_value.__enter__.return_value = mock_response

            installer = EmbedInstaller(
                root_dir=tmp_path,
                cache_dir=tmp_path,
                offline=False,
                skip_speed_test=True,
            )
            downloader = EmbedDownloader(
                parent=installer,
                version="3.8.10",
            )
            result = downloader.test_url_speed("https://example.com/test.zip", timeout=5)
            assert result >= 0  # Time should be positive

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_timeout_failure(self, mock_urlopen, tmp_path):
        """Test URL speed test with timeout failure."""
        mock_urlopen.side_effect = TimeoutError()

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5)
        assert result == float("inf")  # Indicates failure

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_url_error(self, mock_urlopen, tmp_path):
        """Test URL speed test with URL error."""
        from urllib.error import URLError

        mock_urlopen.side_effect = URLError("Network error")

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5)
        assert result == float("inf")  # Indicates failure

    def test_get_urls_to_test_method(self, tmp_path):
        """Test get_urls_to_test method functionality."""
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        urls = downloader.get_urls_to_test("3.8.10", "amd64")
        assert len(urls) >= 4  # Should have at least 3 mirrors + 1 official
        assert all("3.8.10" in url for url in urls)
        assert all("amd64" in url for url in urls)


class TestFindAvailableUrls:
    """Test find_available_urls method functionality."""

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.test_url_speed")
    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.get_urls_to_test")
    def test_find_available_urls_all_success(self, mock_get_urls, mock_test_speed, tmp_path):
        """Test find_available_urls when all URLs are available."""
        mock_get_urls.return_value = [
            "https://mirror1.com/python-3.8.10-embed-amd64.zip",
            "https://mirror2.com/python-3.8.10-embed-amd64.zip",
            "https://official.com/python-3.8.10-embed-amd64.zip",
        ]
        # Mock speeds in order [slow, medium, fast] to test sorting
        mock_test_speed.side_effect = [0.5, 0.3, 0.1]  # Fastest last to test sorting

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.find_available_urls("3.8.10", "amd64", 5)

        # Result should be sorted by speed (fastest first)
        assert len(result) == 3
        # The last URL should be fastest (0.1s) so it should be first in result
        assert "official.com" in result[0]

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.test_url_speed")
    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.get_urls_to_test")
    def test_find_available_urls_some_fail(self, mock_get_urls, mock_test_speed, tmp_path):
        """Test find_available_urls when some URLs fail."""
        mock_get_urls.return_value = [
            "https://mirror1.com/python-3.8.10-embed-amd64.zip",
            "https://mirror2.com/python-3.8.10-embed-amd64.zip",
            "https://official.com/python-3.8.10-embed-amd64.zip",
        ]
        # Mock speeds: first two fail, last one succeeds
        mock_test_speed.side_effect = [float("inf"), float("inf"), 0.1]

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.find_available_urls("3.8.10", "amd64", 5)

        # Only the successful URL should be returned
        assert len(result) == 1
        assert "official.com" in result[0]

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.test_url_speed")
    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.get_urls_to_test")
    def test_find_available_urls_all_fail(self, mock_get_urls, mock_test_speed, tmp_path):
        """Test find_available_urls when all URLs fail, should fall back to official."""
        mock_get_urls.return_value = [
            "https://mirror1.com/python-3.8.10-embed-amd64.zip",
            "https://mirror2.com/python-3.8.10-embed-amd64.zip",
        ]
        # Mock speeds: all fail
        mock_test_speed.side_effect = [float("inf"), float("inf")]

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.find_available_urls("3.8.10", "amd64", 5)

        # Should fall back to official URL
        assert len(result) == 1
        assert "python.org" in result[0]  # Official site


class TestFindLatestPatchOnline:
    """Test find_latest_patch_online method functionality."""

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.check_version_url")
    def test_find_latest_patch_online_success(self, mock_check_version, tmp_path):
        """Test find_latest_patch_online when it finds a version."""

        # Mock to return True only for a specific version
        def mock_check_side_effect(version, arch, timeout):
            return version == "3.8.10"

        mock_check_version.side_effect = mock_check_side_effect

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.find_latest_patch_online("3", "8", "amd64", 5)

        assert result == "3.8.10"

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.check_version_url")
    def test_find_latest_patch_online_not_found(self, mock_check_version, tmp_path):
        """Test find_latest_patch_online when no version is found."""
        # Mock to always return False
        mock_check_version.return_value = False

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.find_latest_patch_online("3", "8", "amd64", 5)

        assert result is None

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.check_version_url")
    def test_find_latest_patch_online_first_patch_found(self, mock_check_version, tmp_path):
        """Test find_latest_patch_online finds the first (highest) patch version."""

        # Mock to return True only for version 3.8.10 (highest for 3.8 embed)
        def mock_check_side_effect(version, arch, timeout):
            return version == "3.8.10"

        mock_check_version.side_effect = mock_check_side_effect

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )
        result = downloader.find_latest_patch_online("3", "8", "amd64", 5)

        # Should find the highest patch version first (searches from 10 down to 0 for 3.8 embed)
        assert result == "3.8.10"

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.check_version_url")
    def test_find_latest_patch_online_3_9_version(self, mock_check_version, tmp_path):
        """Test find_latest_patch_online with Python 3.9 embed version."""

        # Mock to return True only for version 3.9.18 (highest for 3.9 embed)
        def mock_check_side_effect(version, arch, timeout):
            return version == "3.9.18"

        mock_check_version.side_effect = mock_check_side_effect

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.9.18",
        )
        result = downloader.find_latest_patch_online("3", "9", "amd64", 5)

        # Should find 3.9.18 (searches from 18 down to 0 for 3.9 embed)
        assert result == "3.9.18"

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.check_version_url")
    def test_find_latest_patch_online_unknown_version(self, mock_check_version, tmp_path):
        """Test find_latest_patch_online with unknown version defaults to 20."""

        # Mock to return True for a version within default range
        def mock_check_side_effect(version, arch, timeout):
            return version == "3.15.15"  # Unknown version, should check up to patch 20

        mock_check_version.side_effect = mock_check_side_effect

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.15.15",
        )
        result = downloader.find_latest_patch_online("3", "15", "amd64", 5)

        # Should find 3.15.15 (searches from 20 down to 0 for unknown versions)
        assert result == "3.15.15"


class TestGetMaxPatchForVersion:
    """Test _get_max_patch_for_version method functionality."""

    def test_get_max_patch_for_known_versions(self, tmp_path):
        """Test getting max patch for known Python embed versions."""
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        # Test known embed versions
        assert downloader._get_max_patch_for_version("3", "8") == 10  # 3.8.10
        assert downloader._get_max_patch_for_version("3", "9") == 18  # 3.9.18
        assert downloader._get_max_patch_for_version("3", "10") == 15  # 3.10.15
        assert downloader._get_max_patch_for_version("3", "11") == 9  # 3.11.9
        assert downloader._get_max_patch_for_version("3", "12") == 3  # 3.12.3
        assert downloader._get_max_patch_for_version("3", "13") == 1  # 3.13.1

    def test_get_max_patch_for_unknown_version(self, tmp_path):
        """Test getting max patch for unknown Python embed version."""
        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        # Test unknown version should default to 15
        assert downloader._get_max_patch_for_version("3", "15") == 15
        assert downloader._get_max_patch_for_version("4", "0") == 15


class TestTryDownloadFromUrl:
    """Test cases for try_download_from_url method."""

    @patch("pytola.dev.pypack.components.embedinstaller.zipfile.is_zipfile")
    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_try_download_from_url_success(self, mock_urlopen, mock_is_zipfile, tmp_path):
        """Test successful download from URL."""
        # Mock successful download
        mock_response = Mock()
        mock_response.headers.get.return_value = "1000"
        mock_response.read.side_effect = [b"test" * 250, b""]  # 1000 bytes total
        mock_urlopen.return_value.__enter__.return_value = mock_response
        mock_is_zipfile.return_value = True

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        cache_file = tmp_path / "test.zip"
        result = downloader.try_download_from_url(cache_file, "https://example.com/test.zip")

        assert result is True

    @patch("pytola.dev.pypack.components.embedinstaller.zipfile.is_zipfile")
    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_try_download_from_url_corrupted_file(self, mock_urlopen, mock_is_zipfile, tmp_path):
        """Test download with corrupted file."""
        # Mock download that creates corrupted file
        mock_response = Mock()
        mock_response.headers.get.return_value = "10"  # Small size to match actual data
        mock_response.read.side_effect = [b"corrupted!", b""]  # 10 bytes
        mock_urlopen.return_value.__enter__.return_value = mock_response
        mock_is_zipfile.return_value = False

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        cache_file = tmp_path / "test.zip"
        result = downloader.try_download_from_url(cache_file, "https://example.com/test.zip")

        assert result is False
        assert not cache_file.exists()  # Should be cleaned up

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_try_download_from_url_network_error(self, mock_urlopen, tmp_path):
        """Test download with network error."""
        from urllib.error import URLError

        mock_urlopen.side_effect = URLError("Network error")

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        cache_file = tmp_path / "test.zip"
        with pytest.raises(URLError):
            downloader.try_download_from_url(cache_file, "https://example.com/test.zip")

        # Partial file should be cleaned up
        assert not cache_file.exists()


class TestDownloadPythonPackage:
    """Test cases for download_python_package method."""

    def test_download_python_package_offline_no_cache(self, tmp_path, caplog):
        """Test download in offline mode with no cache."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.ERROR):
                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path,
                    offline=False,
                    skip_speed_test=True,
                )
                downloader = EmbedDownloader(
                    parent=installer,
                    version="3.8.10",
                )

                result = downloader.download_python_package(tmp_path / "test.zip", [], offline=True)
                assert result is False
                # Check if the error message is in any log record
                error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
                assert any("Offline mode" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    def test_download_python_package_no_urls(self, tmp_path, caplog):
        """Test download with no available URLs."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.ERROR):
                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path,
                    offline=False,
                    skip_speed_test=True,
                )
                downloader = EmbedDownloader(
                    parent=installer,
                    version="3.8.10",
                )

                result = downloader.download_python_package(tmp_path / "test.zip", [], offline=False)
                assert result is False
                # Check if the error message is in any log record
                error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
                assert any("No available URLs" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.try_download_from_url")
    def test_download_python_package_success_first_url(self, mock_try_download, tmp_path):
        """Test successful download from first URL."""
        mock_try_download.return_value = True

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        urls = ["https://mirror1.com/test.zip", "https://mirror2.com/test.zip"]
        result = downloader.download_python_package(tmp_path / "test.zip", urls, offline=False)
        assert result is True
        assert mock_try_download.call_count == 1  # Should stop after first success

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.try_download_from_url")
    def test_download_python_package_retry_on_failure(self, mock_try_download, tmp_path, caplog):
        """Test download retries with next URL on failure."""
        from urllib.error import URLError

        # First URL fails, second succeeds
        mock_try_download.side_effect = [URLError("Network error"), True]

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.INFO):
                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path,
                    offline=False,
                    skip_speed_test=True,
                )
                downloader = EmbedDownloader(
                    parent=installer,
                    version="3.8.10",
                )

                urls = ["https://mirror1.com/test.zip", "https://mirror2.com/test.zip"]
                result = downloader.download_python_package(tmp_path / "test.zip", urls, offline=False)
                assert result is True
                assert mock_try_download.call_count == 2
                # Check if the info message is in any log record
                info_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.INFO]
                assert any("Retrying with next available URL" in msg for msg in info_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.try_download_from_url")
    def test_download_python_package_all_urls_fail(self, mock_try_download, tmp_path, caplog):
        """Test download fails when all URLs fail."""
        from urllib.error import URLError

        # All URLs fail
        mock_try_download.side_effect = URLError("Network error")

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.ERROR):
                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path,
                    offline=False,
                    skip_speed_test=True,
                )
                downloader = EmbedDownloader(
                    parent=installer,
                    version="3.8.10",
                )

                urls = ["https://mirror1.com/test.zip", "https://mirror2.com/test.zip"]
                result = downloader.download_python_package(tmp_path / "test.zip", urls, offline=False)
                assert result is False
                # Check if the error message is in any log record
                error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
                assert any("Failed to download Python from all available URLs" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestUrlSpeedTestEdgeCases:
    """Test edge cases for URL speed testing with HEAD/GET fallback."""

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_head_forbidden_fallback_to_get(self, mock_urlopen, tmp_path):
        """Test URL speed test with HEAD 403 Forbidden, falls back to GET."""
        import email.message
        from urllib.error import HTTPError

        headers = email.message.Message()
        http_error_403 = HTTPError("https://example.com", 403, "Forbidden", headers, None)

        # Mock: HEAD request returns 403, then GET request succeeds
        mock_response_get = Mock()
        mock_response_get.status = 206  # Partial content
        mock_context = Mock()
        mock_context.__enter__ = Mock(return_value=mock_response_get)
        mock_context.__exit__ = Mock(return_value=False)

        mock_urlopen.side_effect = [http_error_403, mock_context]

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5)
        assert result >= 0  # Should succeed with GET fallback

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_head_non_200_status(self, mock_urlopen, tmp_path):
        """Test URL speed test with HEAD returning non-200 status."""
        mock_response = Mock()
        mock_response.status = 301  # Redirect
        mock_urlopen.return_value.__enter__.return_value = mock_response

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5)
        assert result == float("inf")  # Should fail

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_get_fallback_status_200(self, mock_urlopen, tmp_path):
        """Test GET fallback with status 200 (full content)."""
        mock_response = Mock()
        mock_response.status = 200  # Full content instead of partial
        mock_urlopen.return_value.__enter__.return_value = mock_response

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5, use_get=True)
        assert result >= 0  # Should succeed with status 200

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_get_fallback_non_206_status(self, mock_urlopen, tmp_path):
        """Test GET fallback with non-206/200 status."""
        mock_response = Mock()
        mock_response.status = 404  # Not found
        mock_urlopen.return_value.__enter__.return_value = mock_response

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5, use_get=True)
        assert result == float("inf")  # Should fail

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_url_speed_http_error_non_405_403(self, mock_urlopen, tmp_path):
        """Test URL speed test with HTTP error other than 405/403."""
        import email.message
        from urllib.error import HTTPError

        headers = email.message.Message()
        http_error_500 = HTTPError("https://example.com", 500, "Server Error", headers, None)
        mock_urlopen.side_effect = http_error_500

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        result = downloader.test_url_speed("https://example.com/test.zip", timeout=5)
        assert result == float("inf")  # Should fail without fallback


class TestInstallPackageIntegration:
    """Test install_package method integration."""

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.download")
    @patch("pytola.dev.pypack.components.embedinstaller.Solution.from_directory")
    def test_install_package_download_failure(self, mock_from_directory, mock_download, tmp_path, caplog):
        """Test install_package when download fails."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.ERROR):
                mock_download.return_value = False

                # Create a mock project with min_python_version
                mock_project = Mock()
                mock_project.name = "test-project"
                mock_project.min_python_version = "3.8.10"

                mock_solution = Mock()
                mock_solution.projects = {"test-project": mock_project}

                mock_from_directory.return_value = mock_solution

                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path / "cache",
                    offline=False,
                    skip_speed_test=True,
                )

                result = installer.run()
                assert result is False
                # Check if the error message is in any log record
                error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
                assert any("Failed to download" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedInstaller.extract_package")
    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.download")
    @patch("pytola.dev.pypack.components.embedinstaller.Solution.from_directory")
    def test_install_package_extract_failure(self, mock_from_directory, mock_download, mock_extract, tmp_path, caplog):
        """Test install_package when extraction fails."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.ERROR):
                mock_download.return_value = True
                mock_extract.return_value = False

                # Create a mock project with min_python_version
                mock_project = Mock()
                mock_project.name = "test-project"
                mock_project.min_python_version = "3.8.10"

                mock_solution = Mock()
                mock_solution.projects = {"test-project": mock_project}

                mock_from_directory.return_value = mock_solution

                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path / "cache",
                    offline=False,
                    skip_speed_test=True,
                )

                result = installer.run()
                assert result is False
                # Check if the error message is in any log record
                error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
                assert any("Failed to extract" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestParseArgs:
    """Test command-line argument parsing."""

    def test_parse_args_defaults(self):
        """Test parse_args with default values."""
        with patch("sys.argv", ["pyembedinstall"]):
            args = parse_args()
            assert args.version == "3.8.10"
            assert args.debug is False
            assert args.offline is False
            assert args.skip_speed_test is False
            assert args.timeout == 5

    def test_parse_args_custom_version(self):
        """Test parse_args with custom version."""
        with patch("sys.argv", ["pyembedinstall", "--version", "3.9.0"]):
            args = parse_args()
            assert args.version == "3.9.0"

    def test_parse_args_debug_mode(self):
        """Test parse_args with debug mode enabled."""
        with patch("sys.argv", ["pyembedinstall", "--debug"]):
            args = parse_args()
            assert args.debug is True

    def test_parse_args_offline_mode(self):
        """Test parse_args with offline mode enabled."""
        with patch("sys.argv", ["pyembedinstall", "--offline"]):
            args = parse_args()
            assert args.offline is True

    def test_parse_args_skip_speed_test(self):
        """Test parse_args with skip speed test option."""
        with patch("sys.argv", ["pyembedinstall", "--skip-speed-test"]):
            args = parse_args()
            assert args.skip_speed_test is True

    def test_parse_args_custom_timeout(self):
        """Test parse_args with custom timeout."""
        with patch("sys.argv", ["pyembedinstall", "--timeout", "10"]):
            args = parse_args()
            assert args.timeout == 10

    def test_parse_args_custom_directory(self):
        """Test parse_args with custom directory."""
        with patch("sys.argv", ["pyembedinstall", "/custom/path"]):
            args = parse_args()
            assert args.directory == "/custom/path"

    def test_parse_args_custom_cache_dir(self):
        """Test parse_args with custom cache directory."""
        with patch("sys.argv", ["pyembedinstall", "--cache-dir", "/custom/cache"]):
            args = parse_args()
            assert args.cache_dir == "/custom/cache"


class TestHypothesisPropertyTests:
    """Property-based tests using hypothesis."""

    @given(
        st.integers(min_value=3, max_value=3),
        st.integers(min_value=6, max_value=13),
        st.integers(min_value=0, max_value=50),
    )
    def test_version_extraction_idempotent(self, major, minor, patch):
        """Test that version extraction is idempotent."""
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_path = Path(tmp_dir)
            installer = EmbedInstaller(
                root_dir=tmp_path,
                cache_dir=tmp_path,
                offline=False,
                skip_speed_test=True,
            )
            downloader = EmbedDownloader(
                parent=installer,
                version="3.8.10",
            )
            version = f"{major}.{minor}.{patch}"
            filename = f"python-{version}-embed-amd64"
            result1 = downloader.extract_version_from_filename(filename, "amd64")
            result2 = downloader.extract_version_from_filename(filename, "amd64")
            assert result1 == result2 == version

    @given(
        st.integers(min_value=3, max_value=3),
        st.integers(min_value=6, max_value=13),
        st.integers(min_value=0, max_value=50),
    )
    def test_version_format_validation(self, major, minor, patch):
        """Test version format validation with various inputs."""
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_path = Path(tmp_dir)
            installer = EmbedInstaller(
                root_dir=tmp_path,
                cache_dir=tmp_path,
                offline=False,
                skip_speed_test=True,
            )
            downloader = EmbedDownloader(
                parent=installer,
                version="3.8.10",
            )

            # Test two-part version validation
            two_part = f"{major}.{minor}"
            assert downloader.validate_version_format(two_part) is True

            # Test three-part version validation (should be False)
            three_part = f"{major}.{minor}.{patch}"
            assert downloader.validate_version_format(three_part) is False

    @given(
        st.integers(min_value=3, max_value=3),
        st.integers(min_value=6, max_value=13),
        st.integers(min_value=0, max_value=50),
    )
    def test_version_completeness_check(self, major, minor, patch):
        """Test version completeness check with various inputs."""
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_path = Path(tmp_dir)
            installer = EmbedInstaller(
                root_dir=tmp_path,
                cache_dir=tmp_path,
                offline=False,
                skip_speed_test=True,
            )
            downloader = EmbedDownloader(
                parent=installer,
                version="3.8.10",
            )

            # Test two-part version (incomplete)
            two_part = f"{major}.{minor}"
            assert downloader.is_version_complete(two_part) is False

            # Test three-part version (complete)
            three_part = f"{major}.{minor}.{patch}"
            assert downloader.is_version_complete(three_part) is True

    @given(st.sampled_from(["amd64", "arm64"]))
    def test_cache_file_path_consistency(self, arch):
        """Test cache file path is consistent across calls."""
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_path = Path(tmp_dir)
            installer = EmbedInstaller(
                root_dir=tmp_path,
                cache_dir=tmp_path,
                offline=False,
                skip_speed_test=True,
            )
            downloader = EmbedDownloader(
                parent=installer,
                version="3.8.10",
            )

            # Access cache_file multiple times
            path1 = downloader.cache_file
            path2 = downloader.cache_file

            # Should be the same object (cached_property)
            assert path1 is path2
            # Check that the actual architecture of the system is in the path
            assert installer.arch in str(path1)


class TestExtractPackageEdgeCases:
    """Test edge cases for package extraction."""

    def test_extract_package_with_badzip_exception(self, tmp_path, caplog):
        """Test extract_package with BadZipFile exception during extraction."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            with caplog.at_level(logging.ERROR):
                # Create a valid zip file
                cache_file = tmp_path / "cache" / "python-3.8.10-embed-amd64.zip"
                cache_file.parent.mkdir(parents=True, exist_ok=True)

                with zipfile.ZipFile(cache_file, "w") as zf:
                    zf.writestr("test.txt", "content")

                installer = EmbedInstaller(
                    root_dir=tmp_path,
                    cache_dir=tmp_path / "cache",
                    offline=False,
                    skip_speed_test=True,
                )

                # Create a downloader instance to pass to extract_package
                downloader = EmbedDownloader(
                    parent=installer,
                    version="3.8.10",
                )

                # Mock shutil.unpack_archive to raise BadZipFile
                with patch("pytola.dev.pypack.components.embedinstaller.shutil.unpack_archive") as mock_unpack:
                    mock_unpack.side_effect = zipfile.BadZipFile("Corrupted during extraction")
                    result = installer.extract_package(downloader)
                    assert result is False
                    # Check if the error message is in any log record
                    error_messages = [
                        record.getMessage() for record in caplog.records if record.levelno == logging.ERROR
                    ]
                    assert any("Failed to extract" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestArchitectureDetection:
    """Test architecture detection in EmbedInstaller."""

    @pytest.mark.parametrize(
        ("machine_arch", "expected_arch"),
        [
            ("AMD64", "amd64"),
            ("x86_64", "amd64"),
            ("arm64", "arm64"),
            ("aarch64", "arm64"),
            ("unknown_arch", "amd64"),  # Default fallback
        ],
    )
    @patch("pytola.dev.pypack.components.embedinstaller.platform.machine")
    def test_arch_detection(self, mock_machine, machine_arch, expected_arch, tmp_path):
        """Test architecture detection with various machine types."""
        mock_machine.return_value = machine_arch

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path / "cache",
            offline=False,
            skip_speed_test=True,
        )

        assert installer.arch == expected_arch


class TestDownloadFileChunksProgress:
    """Test download file chunks with progress logging."""

    @patch("pytola.dev.pypack.components.embedinstaller.time.time")
    def test_download_file_chunks_progress_logging(self, mock_time, tmp_path):
        """Test that progress is logged at appropriate intervals."""
        # Mock time to control progress logging
        time_sequence = [0.0, 0.3, 0.6, 1.0, 1.5, 2.0]
        mock_time.side_effect = time_sequence

        mock_response = Mock()
        chunk_size = 1024 * 1024  # 1 MB chunks
        total_size = chunk_size * 3  # 3 MB total
        mock_response.read.side_effect = [
            b"x" * chunk_size,
            b"x" * chunk_size,
            b"x" * chunk_size,
            b"",
        ]

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        dest_path = tmp_path / "test.zip"
        downloaded = downloader.download_file_chunks(mock_response, dest_path, total_size)

        assert downloaded == total_size
        assert dest_path.exists()

    def test_download_file_chunks_zero_size(self, tmp_path):
        """Test downloading file with unknown size (content-length = 0)."""
        mock_response = Mock()
        chunk = b"test_data"
        mock_response.read.side_effect = [chunk, chunk, b""]

        installer = EmbedInstaller(
            root_dir=tmp_path,
            cache_dir=tmp_path,
            offline=False,
            skip_speed_test=True,
        )
        downloader = EmbedDownloader(
            parent=installer,
            version="3.8.10",
        )

        dest_path = tmp_path / "test.zip"
        downloaded = downloader.download_file_chunks(mock_response, dest_path, 0)

        # Should download successfully even with unknown size
        assert downloaded == len(chunk) * 2
        assert dest_path.exists()


class TestOSErrorHandling:
    """Test cases for OSError exception handling."""

    def test_has_cached_file_delete_failure(self, embed_downloader, caplog):
        """Test handling OSError when deleting corrupted cache file."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock unlink to raise OSError
            with patch.object(Path, "unlink", side_effect=OSError("Permission denied")), caplog.at_level(
                logging.WARNING
            ):
                zip_path = embed_downloader.cache_file
                # Create a corrupted file (non-zip)
                with zip_path.open("w") as f:
                    f.write("corrupted content")

                # Clear cached property
                if "has_cached_file" in embed_downloader.__dict__:
                    del embed_downloader.__dict__["has_cached_file"]
                result = embed_downloader.has_cached_file

            assert result is False
            # Check if the warning message is in any log record
            warning_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.WARNING]
            assert any("Failed to delete corrupted cache file" in msg for msg in warning_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_try_download_from_url_delete_failure(self, mock_urlopen, embed_downloader, temp_path):
        """Test handling OSError when deleting partial download."""
        from urllib.error import URLError

        # Mock download failure
        mock_urlopen.side_effect = URLError("Network error")

        cache_file = temp_path / "test.zip"
        cache_file.touch()  # Create partial file

        # OSError during unlink will be suppressed by suppress(OSError)
        # We just test that the exception is raised and file cleanup is attempted
        with pytest.raises(URLError):
            embed_downloader.try_download_from_url(cache_file, "http://example.com/test.zip")

        # File should be deleted after failed download (if no OSError)
        # But if OSError happens during unlink, it's suppressed
        # So we can't guarantee the file state


class TestFindAvailableUrlsDebugLogging:
    """Test cases for debug logging in find_available_urls."""

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.test_url_speed")
    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader.get_urls_to_test")
    def test_find_available_urls_with_debug_logging(self, mock_get_urls, mock_test_speed, embed_downloader, caplog):
        """Test find_available_urls with debug logging enabled."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock the URLs to test
            mock_get_urls.return_value = [
                "https://mirror1.com/python-3.8.10-embed-amd64.zip",
                "https://mirror2.com/python-3.8.10-embed-amd64.zip",
                "https://official.com/python-3.8.10-embed-amd64.zip",
            ]

            # Mock speeds in order [slow, medium, fast] to test sorting
            mock_test_speed.side_effect = [
                0.5,
                0.3,
                0.1,
            ]  # Fastest last to test sorting

            # Enable debug logging
            with caplog.at_level(logging.DEBUG):
                # Also set the module logger to DEBUG level
                original_level = _parser.logger.level
                _parser.logger.setLevel(logging.DEBUG)
                try:
                    urls = embed_downloader.find_available_urls("3.8.10", embed_downloader.parent.arch, 5)
                finally:
                    # Restore original level
                    _parser.logger.setLevel(original_level)

            assert len(urls) > 0
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestGetCachedVersion:
    """Test cases for get_cached_version."""

    def test_get_cached_version_no_cache_dir(self, embed_downloader, temp_path):
        """Test get_cached_version when cache directory doesn't exist."""
        non_existent_dir = temp_path / "non_existent"
        result = embed_downloader.get_cached_version("3", "8", embed_downloader.parent.arch, non_existent_dir)
        assert result is None

    def test_get_cached_version_no_matching_files(self, embed_downloader, temp_path):
        """Test get_cached_version when no matching cache files exist."""
        cache_dir = temp_path / "cache"
        cache_dir.mkdir(parents=True, exist_ok=True)

        # Create some non-matching files
        (cache_dir / "python-3.9.0-embed-amd64.zip").touch()

        result = embed_downloader.get_cached_version("3", "8", embed_downloader.parent.arch, cache_dir)
        assert result is None


class TestCheckVersionUrlNon200:
    """Test cases for check_version_url with non-200 status."""

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_check_version_url_non_200_status(self, mock_urlopen, embed_downloader, caplog):
        """Test check_version_url with non-200 HTTP status."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            mock_response = Mock()
            mock_response.status = 301  # Redirect
            mock_urlopen.return_value.__enter__.return_value = mock_response

            with caplog.at_level(logging.DEBUG):
                # Also set the module logger to DEBUG level
                original_level = _parser.logger.level
                _parser.logger.setLevel(logging.DEBUG)
                try:
                    result = embed_downloader.check_version_url("3.8.10", embed_downloader.parent.arch, 5)
                finally:
                    # Restore original level
                    _parser.logger.setLevel(original_level)

            assert result is False
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestResolvePatchVersionWarning:
    """Test cases for resolve_patch_version warning branches."""

    def test_resolve_patch_version_not_found_warning(self, embed_downloader, temp_path, caplog):
        """Test resolve_patch_version when no patch version is found."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            cache_dir = temp_path / "cache"
            cache_dir.mkdir(parents=True, exist_ok=True)

            # Mock check_version_url to always return False (fast failure)
            with patch.object(type(embed_downloader), "check_version_url", return_value=False), caplog.at_level(
                logging.WARNING
            ):
                result = embed_downloader.resolve_patch_version("3.99", embed_downloader.parent.arch, cache_dir, 5)

            assert result == "3.99"  # Should return original version
            # Check if the warning message is in any log record
            warning_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.WARNING]
            assert any("Could not find any patch version for 3.99" in msg for msg in warning_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.urlopen")
    def test_find_latest_patch_online_checking(self, mock_urlopen, embed_downloader, caplog):
        """Test find_latest_patch_online info message."""
        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock first call to succeed
            mock_response = Mock()
            mock_response.status = 200
            mock_urlopen.return_value.__enter__.return_value = mock_response

            with caplog.at_level(logging.INFO):
                result = embed_downloader.find_latest_patch_online("3", "8", embed_downloader.parent.arch, 5)

            assert result is not None
            # Check if the info message is in any log record
            info_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.INFO]
            assert any("Latest version found:" in msg for msg in info_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestEmbedInstallerRunFailures:
    """Test cases for EmbedInstaller.run failure branches."""

    @patch("pytola.dev.pypack.components.embedinstaller.Solution")
    def test_run_no_projects_with_python_version(self, mock_solution, embed_installer, caplog):
        """Test run when no projects have minimum Python version."""
        from types import SimpleNamespace

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock solution with a project without min_python_version
            mock_project = SimpleNamespace(name="test_project", min_python_version=None)
            mock_solution_instance = Mock()
            mock_solution_instance.projects = {"test_project": mock_project}
            mock_solution.from_directory.return_value = mock_solution_instance

            # Clear cached property
            if "solution" in embed_installer.__dict__:
                del embed_installer.__dict__["solution"]

            with caplog.at_level(logging.WARNING):  # Changed to WARNING level
                result = embed_installer.run()

            assert result is False
            # Check for warning message instead
            assert any("does not have a minimum Python version" in record.message for record in caplog.records)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader")
    @patch("pytola.dev.pypack.components.embedinstaller.Solution")
    def test_run_installation_verification_failure(self, mock_solution, mock_downloader_class, embed_installer, caplog):
        """Test run when installation verification fails (python.exe not found)."""
        from types import SimpleNamespace

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock solution with a valid project
            mock_project = SimpleNamespace(name="test_project", min_python_version="3.8")
            mock_solution_instance = Mock()
            mock_solution_instance.projects = {"test_project": mock_project}
            mock_solution.from_directory.return_value = mock_solution_instance

            # Mock downloader to succeed
            mock_downloader = Mock()
            mock_downloader.download.return_value = True
            mock_downloader_class.return_value = mock_downloader

            # Mock extract_package to succeed
            with patch.object(embed_installer, "extract_package", return_value=True), patch.object(
                embed_installer,
                "python_exe_path",
                new_callable=lambda: Mock(exists=lambda: False),
            ), caplog.at_level(logging.ERROR):
                # Clear cached properties
                for prop in ["solution"]:
                    if prop in embed_installer.__dict__:
                        del embed_installer.__dict__[prop]

                result = embed_installer.run()

            assert result is False
            # Check if the error message is in any log record
            error_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.ERROR]
            assert any("Failed to install Python embeddable package" in msg for msg in error_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader")
    @patch("pytola.dev.pypack.components.embedinstaller.Solution")
    def test_run_multiple_projects_extract_once(self, mock_solution, mock_downloader_class, embed_installer, caplog):
        """Test run with multiple projects extracts embed Python only once."""
        from types import SimpleNamespace

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock solution with multiple projects
            mock_project1 = SimpleNamespace(name="test_project1", min_python_version="3.8.10")
            mock_project2 = SimpleNamespace(name="test_project2", min_python_version="3.8.10")
            mock_solution_instance = Mock()
            mock_solution_instance.projects = {
                "test_project1": mock_project1,
                "test_project2": mock_project2,
            }
            mock_solution.from_directory.return_value = mock_solution_instance

            # Mock downloader to succeed
            mock_downloader = Mock()
            mock_downloader.download.return_value = True
            mock_downloader_class.return_value = mock_downloader

            # Mock extract_package and track calls
            extract_calls = []

            def mock_extract(downloader):
                extract_calls.append(1)
                # Create python.exe on first call
                if len(extract_calls) == 1:
                    embed_installer.python_exe_path.parent.mkdir(parents=True, exist_ok=True)
                    embed_installer.python_exe_path.touch()
                return True

            with patch.object(embed_installer, "extract_package", side_effect=mock_extract), caplog.at_level(
                logging.INFO
            ):
                # Clear cached properties
                for prop in ["solution"]:
                    if prop in embed_installer.__dict__:
                        del embed_installer.__dict__[prop]

                result = embed_installer.run()

            # Should succeed
            assert result is True
            # extract_package should only be called once (for the first project)
            assert len(extract_calls) == 1
            # Check if the info messages are in any log record
            info_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.INFO]
            assert any("Processing project: `test_project1`" in msg for msg in info_messages)
            assert any("Processing project: `test_project2`" in msg for msg in info_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("pytola.dev.pypack.components.embedinstaller.EmbedDownloader")
    @patch("pytola.dev.pypack.components.embedinstaller.Solution")
    def test_run_skip_extraction_if_python_exists(self, mock_solution, mock_downloader_class, embed_installer, caplog):
        """Test run skips extraction if python.exe already exists."""
        from types import SimpleNamespace

        import pytola.dev.pypack.components.embedinstaller as cli_module

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock solution with a project
            mock_project = SimpleNamespace(name="test_project", min_python_version="3.8.10")
            mock_solution_instance = Mock()
            mock_solution_instance.projects = {"test_project": mock_project}
            mock_solution.from_directory.return_value = mock_solution_instance

            # Mock downloader to succeed
            mock_downloader = Mock()
            mock_downloader.download.return_value = True
            mock_downloader_class.return_value = mock_downloader

            # Create python.exe beforehand to simulate existing installation
            embed_installer.python_exe_path.parent.mkdir(parents=True, exist_ok=True)
            embed_installer.python_exe_path.touch()

            # Mock extract_package to track calls
            with patch.object(embed_installer, "extract_package") as mock_extract, caplog.at_level(logging.INFO):
                # Clear cached properties
                for prop in ["solution"]:
                    if prop in embed_installer.__dict__:
                        del embed_installer.__dict__[prop]

                result = embed_installer.run()

            # Should succeed
            assert result is True
            # extract_package should not be called at all
            mock_extract.assert_not_called()
            # Should log that extraction was skipped
            # Check if the info messages are in any log record
            info_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.INFO]
            assert any("Python runtime already exists" in msg for msg in info_messages)
            assert any("skipping extraction" in msg for msg in info_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)


class TestMainFunction:
    """Test cases for main function and parse_args."""

    @patch("sys.argv", ["pyembedinstall", "--debug"])
    @patch("pytola.dev.pypack.components.embedinstaller.EmbedInstaller")
    def test_main_with_debug_flag(self, mock_installer_class, caplog):
        """Test main function with debug flag."""
        from ..components import embedinstaller as cli_module
        from ..components.embedinstaller import main

        # Add the caplog handler to the module's logger for this test
        cli_module.logger.addHandler(caplog.handler)
        try:
            # Mock installer to succeed
            mock_installer = Mock()
            mock_installer.run.return_value = True
            mock_installer_class.return_value = mock_installer

            with caplog.at_level(logging.DEBUG):
                main()

            # Check if the info message is in any log record
            info_messages = [record.getMessage() for record in caplog.records if record.levelno == logging.INFO]
            assert any("Installation completed" in msg for msg in info_messages)
        finally:
            # Remove the handler to avoid affecting other tests
            cli_module.logger.removeHandler(caplog.handler)

    @patch("sys.argv", ["pyembedinstall", "/test/path"])
    @patch("pytola.dev.pypack.components.embedinstaller.EmbedInstaller")
    def test_main_installation_failure(self, mock_installer_class):
        """Test main function when installation fails."""
        from ..components.embedinstaller import main

        # Mock installer to fail
        mock_installer = Mock()
        mock_installer.run.return_value = False
        mock_installer_class.return_value = mock_installer

        with pytest.raises(SystemExit) as exc_info:
            main()

        assert exc_info.value.code == 1

    @patch(
        "sys.argv",
        [
            "pyembedinstall",
            "/test/path",
            "--version",
            "3.9.0",
            "--offline",
            "--skip-speed-test",
            "--timeout",
            "10",
            "--cache-dir",
            "/cache",
        ],
    )
    def test_parse_args_all_options(self):
        """Test parse_args with all command-line options."""
        args = parse_args()

        assert args.directory == "/test/path"
        assert args.version == "3.9.0"
        assert args.offline is True
        assert args.skip_speed_test is True
        assert args.timeout == 10
        assert args.cache_dir == "/cache"

    @patch("sys.argv", ["pyembedinstall"])
    def test_parse_args_defaults(self):
        """Test parse_args with default values."""
        args = parse_args()

        assert args.debug is False
        assert args.offline is False
        assert args.skip_speed_test is False
