import os
import subprocess
import pytest
from unittest.mock import Mock, patch, mock_open
from pathlib import Path
from eos_downloader.logics.download import SoftManager
from eos_downloader.logics.arista_xml_server import EosXmlObject


@pytest.fixture
def soft_manager():
    return SoftManager()


@pytest.fixture
def soft_manager_force():
    """SoftManager with force_download enabled."""
    return SoftManager(force_download=True)


@pytest.fixture
def mock_eos_object():
    mock = Mock(spec=EosXmlObject)
    mock.version = "4.28.0F"
    mock.filename = "EOS-4.28.0F.swi"
    mock.urls = {
        "image": "http://example.com/EOS-4.28.0F.swi",
        "md5sum": "http://example.com/EOS-4.28.0F.swi.md5",
        "sha512sum": "http://example.com/EOS-4.28.0F.swi.sha512",
    }
    mock.hash_filename = Mock(return_value="EOS-4.28.0F.swi.md5")
    return mock


@pytest.mark.parametrize(
    "dry_run,force_download",
    [
        (True, False),
        (False, False),
        (False, True),
        (True, True),
    ],
)
def test_soft_manager_init(dry_run, force_download):
    """Test SoftManager initialization with various flags."""
    manager = SoftManager(dry_run=dry_run, force_download=force_download)
    assert manager.dry_run == dry_run
    assert manager.force_download == force_download
    assert manager.file == {"name": None, "md5sum": None, "sha512sum": None}


@patch("requests.get")
@patch("tqdm.tqdm")
def test_download_file_raw(mock_tqdm, mock_requests):
    # Setup mock response
    mock_response = Mock()
    mock_response.headers = {"Content-Length": "1024"}
    mock_response.iter_content.return_value = [b"data"]
    mock_requests.return_value = mock_response

    with patch("builtins.open", mock_open()) as mock_file:
        result = SoftManager._download_file_raw("http://test.com/file", "/tmp/file")
        assert result == "/tmp/file"
        mock_file().write.assert_called_with(b"data")


@patch("os.makedirs")
def test_create_destination_folder(mock_makedirs):
    SoftManager._create_destination_folder("/test/path")
    mock_makedirs.assert_called_once_with("/test/path", exist_ok=True)


def test_compute_hash_md5sum(soft_manager):
    test_data = b"test data"
    expected_hash = "eb733a00c0c9d336e65691a37ab54293"

    with patch("builtins.open", mock_open(read_data=test_data)):
        result = soft_manager._compute_hash_md5sum("test_file", expected_hash)
        assert result is True

        # Test with incorrect hash
        result = soft_manager._compute_hash_md5sum("test_file", "wrong_hash")
        assert result is False


# @pytest.mark.parametrize(
#     "check_type,valid_hash", [("md5sum", True), ("sha512sum", True)]
# )
# def test_checksum(soft_manager, check_type, valid_hash):
#     soft_manager.file = {
#         "name": "test.swi",
#         "md5sum": "test.swi.md5",
#         "sha512sum": "test.swi.sha512",
#     }

#     mock_hash = "a" * (128 if check_type == "sha512sum" else 32)

#     with patch(
#         "builtins.open",
#         mock_open(read_data=f"{mock_hash if valid_hash else 'wrong'} test.swi"),
#     ) as mock_file:
#         if valid_hash:
#             with patch(
#                 "hashlib.sha512" if check_type == "sha512sum" else "hashlib.md5"
#             ) as mock_hash_func:
#                 mock_hash_instance = Mock()
#                 mock_hash_instance.hexdigest.return_value = mock_hash
#                 mock_hash_func.return_value = mock_hash_instance
#                 assert soft_manager.checksum(check_type) is True
#         else:
#             with pytest.raises(ValueError):
#                 soft_manager.checksum(check_type)


@patch("eos_downloader.logics.download.SoftManager._download_file_raw")
@patch("eos_downloader.helpers.DownloadProgressBar")
def test_download_file(mock_progress_bar, mock_download_raw, soft_manager):
    url = "http://test.com/file"
    file_path = "/tmp"
    filename = "test.swi"

    # Test with rich interface
    result = soft_manager.download_file(url, file_path, filename, rich_interface=True)
    assert result == os.path.join(file_path, filename)
    mock_progress_bar.assert_called_once()


@patch("eos_downloader.logics.download.SoftManager.download_file")
def test_downloads(mock_download, soft_manager, mock_eos_object):
    path, was_cached = soft_manager.downloads(
        mock_eos_object, "/tmp/downloads", rich_interface=True
    )
    assert path == "/tmp/downloads"
    assert isinstance(was_cached, bool)
    assert mock_download.call_count == len(mock_eos_object.urls)


@patch("shutil.which")
@patch("subprocess.run")
def test_import_docker(mock_run, mock_which, soft_manager):
    mock_which.return_value = "/usr/bin/docker"

    # Test with existing file
    with patch("os.path.exists", return_value=True):
        was_cached = soft_manager.import_docker(
            "/tmp/test.swi", "arista/ceos", "latest"
        )
        assert isinstance(was_cached, bool)
        mock_run.assert_called_once()

    # Test with non-existing file
    with patch("os.path.exists", return_value=False):
        with pytest.raises(FileNotFoundError):
            soft_manager.import_docker("/tmp/nonexistent.swi")


@patch("subprocess.run")
@patch("shutil.which")
@patch("os.path.exists")
def test_provision_eve(
    mock_exists, mock_which, mock_run, soft_manager, mock_eos_object
):
    mock_exists.side_effect = lambda x: x != "/opt/unetlab/addons/qemu/veos-4.29.3M"
    mock_which.return_value = "/usr/bin/qemu-img"

    with patch("eos_downloader.logics.download.SoftManager.download_file"):
        soft_manager.provision_eve(mock_eos_object, noztp=False)
        # Check if qemu-img convert was called (and unl_wrapper if path exists)
        assert mock_run.call_count >= 1


# ============================================================================
# CACHE TESTS - Phase 5
# ============================================================================


class TestFileCache:
    """Test suite for file caching functionality."""

    @patch("pathlib.Path.exists")
    def test_file_exists_and_valid_file_not_found(self, mock_exists):
        """Test that cache check returns False when file doesn't exist."""
        mock_exists.return_value = False
        manager = SoftManager()
        result = manager._file_exists_and_valid(Path("/tmp/test.swi"))
        assert result is False

    @patch("pathlib.Path.exists")
    def test_file_exists_and_valid_skip_checksum(self, mock_exists):
        """Test cache check with skip checksum returns True if file exists."""
        mock_exists.return_value = True
        manager = SoftManager()
        result = manager._file_exists_and_valid(
            Path("/tmp/test.swi"), check_type="skip"
        )
        assert result is True

    @patch("pathlib.Path.exists")
    @patch("eos_downloader.logics.download.SoftManager.checksum")
    def test_file_exists_and_valid_with_checksum(self, mock_checksum, mock_exists):
        """Test cache check validates checksum when requested."""
        mock_exists.return_value = True
        mock_checksum.return_value = True
        manager = SoftManager()
        manager.file = {"name": "test.swi"}

        result = manager._file_exists_and_valid(
            Path("/tmp/test.swi"),
            checksum_file=Path("/tmp/test.swi.md5"),
            check_type="md5sum",
        )
        assert result is True
        mock_checksum.assert_called_once_with(check_type="md5sum")

    @patch("pathlib.Path.exists")
    @patch("eos_downloader.logics.download.SoftManager.checksum")
    def test_file_exists_and_valid_checksum_fails(self, mock_checksum, mock_exists):
        """Test cache check returns False when checksum validation fails."""
        mock_exists.return_value = True
        mock_checksum.side_effect = ValueError("Checksum mismatch")
        manager = SoftManager()
        manager.file = {"name": "test.swi"}

        result = manager._file_exists_and_valid(
            Path("/tmp/test.swi"),
            checksum_file=Path("/tmp/test.swi.md5"),
            check_type="md5sum",
        )
        assert result is False

    @patch("pathlib.Path.exists")
    @patch("eos_downloader.logics.download.SoftManager._download_file_raw")
    def test_download_file_uses_cache(self, mock_download, mock_exists):
        """Test that download_file uses cached file when available."""
        mock_exists.return_value = True
        manager = SoftManager()

        cached_path = manager.download_file(
            "http://test.com/file.swi", "/tmp", "test.swi", rich_interface=False
        )

        # Should return cached path without downloading
        assert cached_path == "/tmp/test.swi"
        mock_download.assert_not_called()

    @patch("pathlib.Path.exists")
    @patch("eos_downloader.logics.download.SoftManager._download_file_raw")
    def test_download_file_bypasses_cache_with_force(self, mock_download, mock_exists):
        """Test that force flag bypasses cache."""
        mock_exists.return_value = True
        mock_download.return_value = "/tmp/test.swi"
        manager = SoftManager(force_download=True)

        downloaded_path = manager.download_file(
            "http://test.com/file.swi", "/tmp", "test.swi", rich_interface=False
        )

        # Should download despite cache
        assert downloaded_path is not None
        mock_download.assert_called_once()

    @patch("pathlib.Path.exists")
    @patch("eos_downloader.logics.download.SoftManager._download_file_raw")
    def test_download_file_force_parameter(self, mock_download, mock_exists):
        """Test that force parameter overrides cache."""
        mock_exists.return_value = True
        mock_download.return_value = "/tmp/test.swi"
        manager = SoftManager()

        forced_path = manager.download_file(
            "http://test.com/file.swi",
            "/tmp",
            "test.swi",
            rich_interface=False,
            force=True,
        )

        # Should download despite cache
        assert forced_path is not None
        mock_download.assert_called_once()


class TestDockerCache:
    """Test suite for Docker image caching functionality."""

    @patch("subprocess.run")
    @patch("shutil.which")
    def test_docker_image_exists_found(self, mock_which, mock_run):
        """Test that _docker_image_exists returns True when image found."""
        mock_which.return_value = "/usr/bin/docker"
        mock_result = Mock()
        mock_result.stdout = "abc123def456\n"
        mock_run.return_value = mock_result

        result = SoftManager._docker_image_exists("arista/ceos", "4.29.3M")
        assert result is True

    @patch("subprocess.run")
    @patch("shutil.which")
    def test_docker_image_exists_not_found(self, mock_which, mock_run):
        """Test that _docker_image_exists returns False when image missing."""
        mock_which.return_value = "/usr/bin/docker"
        mock_result = Mock()
        mock_result.stdout = ""
        mock_run.return_value = mock_result

        result = SoftManager._docker_image_exists("arista/ceos", "4.29.3M")
        assert result is False

    @patch("shutil.which")
    def test_docker_image_exists_no_docker(self, mock_which):
        """Test graceful handling when docker/podman not available."""
        mock_which.return_value = None

        result = SoftManager._docker_image_exists("arista/ceos", "4.29.3M")
        assert result is False

    @patch("subprocess.run")
    @patch("shutil.which")
    def test_docker_image_exists_podman_fallback(self, mock_which, mock_run):
        """Test that podman is tried when docker is unavailable."""
        # Docker not found, podman found
        mock_which.side_effect = lambda x: (
            "/usr/bin/podman" if x == "podman" else None
        )
        mock_result = Mock()
        mock_result.stdout = "image123\n"
        mock_run.return_value = mock_result

        result = SoftManager._docker_image_exists("arista/ceos", "4.29.3M")
        assert result is True

    @patch("subprocess.run")
    @patch("shutil.which")
    def test_docker_image_exists_timeout(self, mock_which, mock_run):
        """Test handling of subprocess timeout."""
        mock_which.return_value = "/usr/bin/docker"
        mock_run.side_effect = subprocess.TimeoutExpired(cmd="docker images", timeout=5)

        result = SoftManager._docker_image_exists("arista/ceos", "4.29.3M")
        assert result is False

    @patch("eos_downloader.logics.download.SoftManager._docker_image_exists")
    @patch("os.path.exists")
    @patch("os.system")
    @patch("shutil.which")
    def test_import_docker_uses_cache(
        self, mock_which, mock_system, mock_exists, mock_docker_exists
    ):
        """Test that import_docker skips import when image exists."""
        mock_which.return_value = "/usr/bin/docker"
        mock_exists.return_value = True
        mock_docker_exists.return_value = True

        manager = SoftManager()
        was_cached = manager.import_docker("/tmp/test.tar", "arista/ceos", "4.29.3M")

        # Should not call docker import
        mock_system.assert_not_called()
        # Should return True (was cached)
        assert was_cached is True

    @patch("eos_downloader.logics.download.SoftManager._docker_image_exists")
    @patch("os.path.exists")
    @patch("subprocess.run")
    @patch("shutil.which")
    def test_import_docker_force_reimport(
        self, mock_which, mock_run, mock_exists, mock_docker_exists
    ):
        """Test that force flag causes re-import."""
        mock_which.return_value = "/usr/bin/docker"
        mock_exists.return_value = True
        mock_docker_exists.return_value = True

        manager = SoftManager()
        was_cached = manager.import_docker(
            "/tmp/test.tar", "arista/ceos", "4.29.3M", force=True
        )

        # Should call docker import despite cache
        mock_run.assert_called_once()
        # Should return False (was imported, not cached)
        assert was_cached is False

    @patch("eos_downloader.logics.download.SoftManager._docker_image_exists")
    @patch("os.path.exists")
    @patch("subprocess.run")
    @patch("shutil.which")
    def test_import_docker_force_download_flag(
        self, mock_which, mock_run, mock_exists, mock_docker_exists
    ):
        """Test that force_download flag causes re-import."""
        mock_which.return_value = "/usr/bin/docker"
        mock_exists.return_value = True
        mock_docker_exists.return_value = True

        manager = SoftManager(force_download=True)
        was_cached = manager.import_docker("/tmp/test.tar", "arista/ceos", "4.29.3M")

        # Should call docker import
        mock_run.assert_called_once()
        # Should return False (was imported, not cached)
        assert was_cached is False


class TestCacheIntegration:
    """Integration tests for cache functionality."""

    @patch("eos_downloader.logics.download.SoftManager.download_file")
    def test_downloads_propagates_force_flag(self, mock_download):
        """Test that downloads() propagates force_download to download_file."""
        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "test.swi"
        mock_eos.urls = {"image": "http://test.com/test.swi"}
        mock_eos.hash_filename = Mock(return_value="test.swi.md5")

        manager = SoftManager(force_download=True)
        path, was_cached = manager.downloads(mock_eos, "/tmp")

        # Verify force was passed to download_file
        mock_download.assert_called()
        call_kwargs = mock_download.call_args[1]
        assert call_kwargs.get("force") is True
        assert path == "/tmp"
        assert isinstance(was_cached, bool)

    @patch("pathlib.Path.exists")
    def test_dry_run_with_cache(self, mock_exists):
        """Test dry-run mode reports cache status correctly."""
        mock_exists.return_value = True

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "test.swi"
        mock_eos.urls = {"image": "http://test.com/test.swi"}
        mock_eos.hash_filename = Mock(return_value="test.swi.md5")

        manager = SoftManager(dry_run=True)
        path, was_cached = manager.downloads(mock_eos, "/tmp")

        # Should complete without actual download
        assert path == "/tmp"
        assert was_cached is True  # File exists and dry-run, so cached


class TestChecksumMethods:
    """Test suite for checksum validation methods."""

    def test_checksum_dry_run_mode(self):
        """Test that checksum returns True in dry-run mode."""
        manager = SoftManager(dry_run=True)
        result = manager.checksum("sha512sum")
        assert result is True

    def test_checksum_sha512sum_file_or_hash_none(self):
        """Test checksum raises ValueError when file or hash is None."""
        manager = SoftManager()
        manager.file = {"name": None, "sha512sum": None}

        with pytest.raises(ValueError, match="File or checksum not found"):
            manager.checksum("sha512sum")

    def test_checksum_md5sum_file_none(self):
        """Test checksum raises ValueError when md5sum file is None."""
        manager = SoftManager()
        manager.file = {"name": "test.swi", "md5sum": None}

        with pytest.raises(ValueError, match="md5sum is not found"):
            manager.checksum("md5sum")

    def test_checksum_unsupported_type(self):
        """Test checksum raises ValueError for unsupported check type."""
        manager = SoftManager()
        manager.file = {"name": "test.swi"}

        with pytest.raises(ValueError, match="Checksum type .* not yet supported"):
            manager.checksum("crc32")

    def test_checksum_md5_alias(self, tmp_path):
        """Test that 'md5' is accepted as alias for 'md5sum'."""
        test_file = tmp_path / "test.swi"
        test_file.write_bytes(b"test content")

        md5_file = tmp_path / "test.swi.md5"
        expected_hash = "9a0364b9e99bb480dd25e1f0284c8555"
        md5_file.write_text(f"{expected_hash}  test.swi")

        manager = SoftManager()
        manager.file = {
            "name": str(test_file),
            "md5sum": str(md5_file),
        }

        with patch.object(manager, "_compute_hash_md5sum", return_value=True):
            result = manager.checksum("md5")
            assert result is True

    def test_checksum_md5sum_filename_none(self, tmp_path):
        """Test checksum raises ValueError when filename is None."""
        md5_file = tmp_path / "test.swi.md5"
        md5_file.write_text("abc123  test.swi")

        manager = SoftManager()
        manager.file = {
            "name": None,
            "md5sum": str(md5_file),
        }

        with pytest.raises(ValueError, match="Filename is None"):
            manager.checksum("md5sum")


class TestDownloadFileEdgeCases:
    """Test suite for download_file edge cases."""

    def test_download_file_url_is_false(self):
        """Test download_file returns None when URL is False."""
        manager = SoftManager()
        result = manager.download_file(
            url=False,
            file_path="/tmp",
            filename="test.swi",
            rich_interface=False,
        )
        assert result is None

    def test_download_file_dry_run_returns_path(self):
        """Test download_file returns path in dry-run mode."""
        manager = SoftManager(dry_run=True)
        result = manager.download_file(
            url="http://test.com/file.swi",
            file_path="/tmp",
            filename="test.swi",
            rich_interface=False,
        )
        assert result == "/tmp/test.swi"


class TestDownloadsEdgeCases:
    """Test suite for downloads method edge cases."""

    def test_downloads_empty_urls_raises_error(self):
        """Test that downloads raises ValueError when urls is empty."""
        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "test.swi"
        mock_eos.urls = {}

        manager = SoftManager()

        with pytest.raises(ValueError, match="Filename not found for version"):
            manager.downloads(mock_eos, "/tmp")

    def test_downloads_url_none_raises_error(self):
        """Test that downloads raises ValueError when URL is None."""
        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "test.swi"
        mock_eos.urls = {"image": None}
        mock_eos.hash_filename = Mock(return_value="test.swi.md5")

        manager = SoftManager()

        with pytest.raises(ValueError, match="URL not found for"):
            manager.downloads(mock_eos, "/tmp")

    def test_downloads_filename_none_raises_error(self):
        """Test that downloads raises ValueError when filename is None."""
        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = None
        mock_eos.urls = {"image": "http://test.com/test.swi"}
        mock_eos.hash_filename = Mock(return_value=None)

        manager = SoftManager()

        with pytest.raises(ValueError, match="Filename not found for"):
            manager.downloads(mock_eos, "/tmp")

    @patch("pathlib.Path.exists")
    def test_downloads_dry_run_not_cached(self, mock_exists):
        """Test dry-run mode with non-cached file reports correctly."""
        mock_exists.return_value = False

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "test.swi"
        mock_eos.urls = {"image": "http://test.com/test.swi"}
        mock_eos.hash_filename = Mock(return_value="test.swi.md5")

        manager = SoftManager(dry_run=True)
        path, was_cached = manager.downloads(mock_eos, "/tmp")

        assert path == "/tmp"
        assert was_cached is False


class TestDockerImageEdgeCases:
    """Test suite for Docker image handling edge cases."""

    @patch("subprocess.run")
    @patch("shutil.which")
    def test_docker_image_exists_subprocess_error(self, mock_which, mock_run):
        """Test graceful handling of subprocess errors."""
        mock_which.return_value = "/usr/bin/docker"
        mock_run.side_effect = subprocess.SubprocessError("Command failed")

        result = SoftManager._docker_image_exists("arista/ceos", "4.29.3M")
        assert result is False

    @patch("subprocess.run")
    @patch("shutil.which")
    def test_docker_image_exists_os_error(self, mock_which, mock_run):
        """Test graceful handling of OS errors."""
        mock_which.return_value = "/usr/bin/docker"
        mock_run.side_effect = OSError("Permission denied")

        result = SoftManager._docker_image_exists("arista/ceos", "4.29.3M")
        assert result is False


class TestImportDockerEdgeCases:
    """Test suite for import_docker method edge cases."""

    @patch("os.path.exists")
    @patch("shutil.which")
    def test_import_docker_no_docker_binary(self, mock_which, mock_exists):
        """Test that import_docker raises FileNotFoundError when docker not found."""
        mock_exists.return_value = True
        mock_which.return_value = None

        manager = SoftManager()

        with pytest.raises(FileNotFoundError, match="Docker binary not found"):
            manager.import_docker("/tmp/test.tar", "arista/ceos", "4.29.3M")

    def test_import_docker_dry_run_returns_false(self):
        """Test import_docker returns False in dry-run mode."""
        manager = SoftManager(dry_run=True)

        with patch("os.path.exists", return_value=True):
            result = manager.import_docker("/tmp/test.tar", "arista/ceos", "4.29.3M")
            assert result is False


class TestChecksumSha512:
    """Test sha512sum checksum match and mismatch (lines 284-297)."""

    def test_checksum_sha512sum_match(self, tmp_path):
        """Test sha512sum checksum match returns True."""
        import hashlib

        test_file = tmp_path / "test.swi"
        test_data = b"test file content for sha512"
        test_file.write_bytes(test_data)

        # Compute expected hash
        hash_sha512 = hashlib.sha512()
        hash_sha512.update(test_data)
        expected_hash = hash_sha512.hexdigest()

        hash_file = tmp_path / "test.swi.sha512sum"
        hash_file.write_text(f"{expected_hash}  test.swi\n")

        manager = SoftManager()
        manager.file = {
            "name": str(test_file),
            "sha512sum": str(hash_file),
        }

        result = manager.checksum("sha512sum")
        assert result is True

    def test_checksum_sha512sum_mismatch(self, tmp_path):
        """Test sha512sum checksum mismatch raises ValueError."""
        test_file = tmp_path / "test.swi"
        test_file.write_bytes(b"test file content")

        hash_file = tmp_path / "test.swi.sha512sum"
        hash_file.write_text("0000wrong_hash  test.swi\n")

        manager = SoftManager()
        manager.file = {
            "name": str(test_file),
            "sha512sum": str(hash_file),
        }

        with pytest.raises(ValueError, match="Incorrect checksum"):
            manager.checksum("sha512sum")

    def test_checksum_md5sum_mismatch(self, tmp_path):
        """Test md5sum checksum mismatch raises ValueError (lines 316-320)."""
        test_file = tmp_path / "test.swi"
        test_file.write_bytes(b"test file content")

        md5_file = tmp_path / "test.swi.md5"
        md5_file.write_text("0000wronghash  test.swi\n")

        manager = SoftManager()
        manager.file = {
            "name": str(test_file),
            "md5sum": str(md5_file),
        }

        with pytest.raises(ValueError, match="Incorrect checksum"):
            manager.checksum("md5sum")


class TestFileExistsValidChecksum:
    """Test _file_exists_and_valid with checksum valid/invalid (lines 142, 148-149)."""

    @patch("pathlib.Path.exists")
    @patch("eos_downloader.logics.download.SoftManager.checksum")
    def test_checksum_invalid_returns_false(self, mock_checksum, mock_exists):
        """Test cache check returns False when checksum is invalid (lines 148-149)."""
        mock_exists.return_value = True
        mock_checksum.return_value = False  # Checksum invalid
        manager = SoftManager()
        manager.file = {"name": "test.swi", "sha512sum": None}

        result = manager._file_exists_and_valid(
            Path("/tmp/test.swi"),
            checksum_file=Path("/tmp/test.swi.sha512sum"),
            check_type="sha512sum",
        )
        assert result is False


class TestImportDockerCalledProcessError:
    """Test import_docker CalledProcessError (lines 663-665)."""

    @patch("subprocess.run")
    @patch("shutil.which")
    @patch("os.path.exists")
    def test_import_docker_called_process_error(self, mock_exists, mock_which, mock_run):
        """Test import_docker raises RuntimeError on CalledProcessError."""
        mock_exists.return_value = True
        mock_which.return_value = "/usr/bin/docker"
        mock_run.side_effect = subprocess.CalledProcessError(
            1, "docker import", stderr="import failed"
        )

        manager = SoftManager()

        with pytest.raises(RuntimeError, match="Docker import failed"):
            manager.import_docker("/tmp/test.tar", "arista/ceos", "4.29.3M")


class TestProvisionEveEdgeCases:
    """Test suite for provision_eve method edge cases."""

    def test_provision_eve_empty_urls_raises_error(self):
        """Test that provision_eve raises ValueError when urls is empty."""
        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "test.vmdk"
        mock_eos.urls = {}

        manager = SoftManager()

        with pytest.raises(ValueError, match="Filename not found for version"):
            manager.provision_eve(mock_eos)

    @patch("subprocess.run")
    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("eos_downloader.logics.download.SoftManager.download_file")
    @patch("eos_downloader.logics.download.SoftManager._create_destination_folder")
    def test_provision_eve_with_noztp(
        self, mock_create_folder, mock_download, mock_exists, mock_which, mock_run
    ):
        """Test provision_eve with noztp flag modifies filename."""
        mock_exists.side_effect = lambda x: x != "/opt/unetlab/addons/qemu/veos-4.29.3M"
        mock_which.return_value = "/usr/bin/qemu-img"

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "vEOS-lab-4.29.3M.vmdk"
        mock_eos.urls = {"image": "http://test.com/vEOS-lab-4.29.3M.vmdk"}
        mock_eos.hash_filename = Mock(return_value="vEOS-lab-4.29.3M.vmdk.md5")

        manager = SoftManager()
        manager.provision_eve(mock_eos, noztp=True)

        # Check that download was called
        mock_download.assert_called()

    @patch("subprocess.run")
    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("eos_downloader.logics.download.SoftManager.download_file")
    @patch("eos_downloader.logics.download.SoftManager._create_destination_folder")
    def test_provision_eve_url_none_raises_error(
        self, mock_create_folder, mock_download, mock_exists, mock_which, mock_run
    ):
        """Test provision_eve raises ValueError when URL is None."""
        mock_exists.return_value = False

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "vEOS-lab-4.29.3M.vmdk"
        mock_eos.urls = {"image": None}
        mock_eos.hash_filename = Mock(return_value="vEOS-lab-4.29.3M.vmdk.md5")

        manager = SoftManager()

        with pytest.raises(ValueError, match="URL not found for"):
            manager.provision_eve(mock_eos)

    @patch("subprocess.run")
    @patch("os.path.exists")
    @patch("eos_downloader.logics.download.SoftManager.download_file")
    @patch("eos_downloader.logics.download.SoftManager._create_destination_folder")
    def test_provision_eve_dry_run_mode(
        self, mock_create_folder, mock_download, mock_exists, mock_run
    ):
        """Test provision_eve in dry-run mode."""
        mock_exists.return_value = True

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "vEOS-lab-4.29.3M.vmdk"
        mock_eos.urls = {"image": "http://test.com/vEOS-lab-4.29.3M.vmdk"}
        mock_eos.hash_filename = Mock(return_value=None)

        manager = SoftManager(dry_run=True)
        manager.provision_eve(mock_eos)

        # subprocess.run should not be called in dry-run mode
        mock_run.assert_not_called()

    @patch("subprocess.run")
    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("eos_downloader.logics.download.SoftManager.download_file")
    @patch("eos_downloader.logics.download.SoftManager._create_destination_folder")
    def test_provision_eve_filename_none_raises_error(
        self, mock_create_folder, mock_download, mock_exists, mock_which, mock_run
    ):
        """Test provision_eve raises ValueError when filename is None (lines 734-735)."""
        mock_exists.return_value = False
        mock_which.return_value = "/usr/bin/qemu-img"

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = None
        mock_eos.urls = {"image": "http://test.com/vEOS.vmdk"}
        mock_eos.hash_filename = Mock(return_value=None)

        manager = SoftManager()

        with pytest.raises(ValueError, match="Filename not found for"):
            manager.provision_eve(mock_eos)

    @patch("subprocess.run")
    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("eos_downloader.logics.download.SoftManager.download_file")
    @patch("eos_downloader.logics.download.SoftManager._create_destination_folder")
    def test_provision_eve_eos_filename_none_raises_error(
        self, mock_create_folder, mock_download, mock_exists, mock_which, mock_run
    ):
        """Test provision_eve raises ValueError when eos_filename is None (line 748)."""
        mock_exists.return_value = False
        mock_which.return_value = "/usr/bin/qemu-img"

        # filename starts as a valid string but hash_filename returns None
        # so the loop processes image but not hash, then eos_filename stays as original
        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        # Set filename property to return None after first call to trigger line 748
        mock_eos.filename = "vEOS-lab-4.29.3M.vmdk"
        mock_eos.urls = {"image": "http://test.com/vEOS-lab-4.29.3M.vmdk"}
        mock_eos.hash_filename = Mock(return_value="vEOS-lab-4.29.3M.vmdk.md5")

        manager = SoftManager()
        # After the download loop, mock eos_filename to be None
        # We need to patch the filename to be None for the qcow2 conversion check
        # Actually line 748 checks eos_filename which is set from object_arista.filename
        # Let's trigger via a different approach - make filename return None initially
        mock_eos2 = Mock()
        mock_eos2.version = "4.29.3M"

        # Use side_effect to return different values for filename property
        type(mock_eos2).filename = property(lambda self: None)
        mock_eos2.urls = {"image": "http://test.com/vEOS.vmdk"}
        mock_eos2.hash_filename = Mock(return_value=None)

        with pytest.raises(ValueError):
            manager.provision_eve(mock_eos2)

    @patch("subprocess.run")
    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("eos_downloader.logics.download.SoftManager.download_file")
    @patch("eos_downloader.logics.download.SoftManager._create_destination_folder")
    def test_provision_eve_qemu_img_not_found(
        self, mock_create_folder, mock_download, mock_exists, mock_which, mock_run
    ):
        """Test provision_eve raises FileNotFoundError when qemu-img not found (line 755)."""
        mock_exists.return_value = True
        mock_which.return_value = None  # qemu-img not found

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "vEOS-lab-4.29.3M.vmdk"
        mock_eos.urls = {"image": "http://test.com/vEOS-lab-4.29.3M.vmdk"}
        mock_eos.hash_filename = Mock(return_value="vEOS-lab-4.29.3M.vmdk.md5")

        manager = SoftManager()

        with pytest.raises(FileNotFoundError, match="qemu-img binary not found"):
            manager.provision_eve(mock_eos)

    @patch("subprocess.run")
    @patch("shutil.which")
    @patch("os.path.exists")
    @patch("eos_downloader.logics.download.SoftManager.download_file")
    @patch("eos_downloader.logics.download.SoftManager._create_destination_folder")
    def test_provision_eve_unl_wrapper_not_found(
        self, mock_create_folder, mock_download, mock_exists, mock_which, mock_run
    ):
        """Test provision_eve logs warning when unl_wrapper not found (line 787)."""
        # File exists for dir check, not for unl_wrapper
        mock_exists.side_effect = lambda x: x != "/opt/unetlab/wrappers/unl_wrapper"
        mock_which.return_value = "/usr/bin/qemu-img"

        mock_eos = Mock()
        mock_eos.version = "4.29.3M"
        mock_eos.filename = "vEOS-lab-4.29.3M.vmdk"
        mock_eos.urls = {"image": "http://test.com/vEOS-lab-4.29.3M.vmdk"}
        mock_eos.hash_filename = Mock(return_value="vEOS-lab-4.29.3M.vmdk.md5")

        manager = SoftManager()
        manager.provision_eve(mock_eos)

        # Should have called qemu-img convert but not unl_wrapper
        assert mock_run.call_count == 1  # Only qemu-img, not unl_wrapper
