#!/usr/bin/env python
# coding: utf-8 -*-
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
"""
Tests for eos_downloader.cli.get.utils module.

This module contains comprehensive tests for all utility functions used
by the get commands, including version search, download, and Docker import.
"""

from __future__ import annotations

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

import pytest
from click.testing import CliRunner

from eos_downloader.cli.get.utils import (
    download_files,
    download_from_containerlab_topology,
    handle_docker_import,
    initialize,
    search_version,
)
from eos_downloader.models.version import EosVersion


# Fixtures


@pytest.fixture
def mock_context():
    """Provide a mock Click context."""
    ctx = Mock()
    ctx.obj = {
        "token": "test-token-abc123",
        "log_level": "info",
        "debug": False,
    }
    return ctx


@pytest.fixture
def mock_console():
    """Provide a mock Rich Console."""
    console = MagicMock()
    return console


@pytest.fixture
def mock_arista_dl_obj():
    """Provide a mock AristaDownloadManager object."""
    dl_obj = MagicMock()
    dl_obj.download_file = MagicMock(return_value=Path("/tmp/test.swi"))
    dl_obj.verify_checksum = MagicMock(return_value=True)
    return dl_obj


# Test Classes


class TestInitialize:
    """Test suite for initialize function."""

    def test_initialize_extracts_context(self, mock_context):
        """Test that initialize correctly extracts context values."""
        console, token, debug, log_level = initialize(mock_context)

        assert token == "test-token-abc123"
        assert log_level == "info"
        assert debug is False

    def test_initialize_with_debug_mode(self):
        """Test initialize with debug mode enabled."""
        ctx = Mock()
        ctx.obj = {
            "token": "debug-token",
            "log_level": "debug",
            "debug": True,
        }

        console, token, debug, log_level = initialize(ctx)

        assert token == "debug-token"
        assert log_level == "debug"
        assert debug is True


class TestSearchVersion:
    """Test suite for search_version function."""

    @patch("eos_downloader.cli.get.utils.AristaXmlQuerier")
    def test_search_specific_version(self, mock_querier_class):
        """Test searching for a specific version."""
        # Setup
        mock_console = MagicMock()
        mock_querier = MagicMock()
        mock_querier_class.return_value = mock_querier

        # Execute
        result = search_version(
            console=mock_console,
            token="test-token",
            version="4.29.3M",
            latest=False,
            branch=None,
            file_format="64",
            release_type="M",
        )

        # Assert
        assert result == "4.29.3M"
        # Should not call AristaXmlQuerier for specific version
        mock_querier_class.assert_not_called()

    @patch("eos_downloader.cli.get.utils.AristaXmlQuerier")
    def test_search_latest_version(self, mock_querier_class):
        """Test searching for the latest version."""
        # Setup
        mock_querier = MagicMock()
        mock_querier_class.return_value = mock_querier
        mock_version = EosVersion.from_str("4.30.1F")
        mock_querier.latest.return_value = mock_version

        # Execute
        result = search_version(
            console=MagicMock(),
            token="test-token",
            version=None,
            latest=True,
            branch=None,
            file_format="64",
            release_type="",  # Empty string instead of None
        )

        # Assert
        assert result == "4.30.1F"  # Latest version as string
        mock_querier.latest.assert_called_once_with(
            package="eos",
            branch=None,
            rtype="F",  # Default to Feature
        )

    @patch("eos_downloader.cli.get.utils.AristaXmlQuerier")
    def test_search_by_branch(self, mock_querier_class):
        """Test searching by branch."""
        # Setup
        mock_querier = MagicMock()
        mock_querier_class.return_value = mock_querier
        mock_version = EosVersion.from_str("4.29.3M")
        mock_querier.latest.return_value = mock_version

        # Execute
        result = search_version(
            console=MagicMock(),
            token="test-token",
            version=None,
            latest=True,
            branch="4.29",
            file_format="64",
            release_type=None,
        )

        # Assert
        assert result == "4.29.3M"
        mock_querier.latest.assert_called_once_with(
            package="eos",
            branch="4.29",
            rtype="F",
        )

    @patch("eos_downloader.cli.get.utils.AristaXmlQuerier")
    def test_search_with_invalid_release_type_defaults(self, mock_querier_class):
        """Test that invalid release type defaults to feature."""
        # Setup
        mock_querier = MagicMock()
        mock_querier_class.return_value = mock_querier
        mock_version = EosVersion.from_str("4.29.3M")
        mock_querier.latest.return_value = mock_version

        # Execute
        search_version(
            console=MagicMock(),
            token="test-token",
            version=None,
            latest=True,
            branch=None,
            file_format="64",
            release_type="invalid",  # Invalid type
        )

        # Assert - should call latest with feature type
        mock_querier.latest.assert_called_once_with(
            package="eos",
            branch=None,
            rtype="F",  # Should default to feature
        )

    @patch("eos_downloader.cli.get.utils.AristaXmlQuerier")
    def test_search_latest_with_branch_and_release_type(self, mock_querier_class):
        """Test searching latest with both branch and release type."""
        # Setup
        mock_querier = MagicMock()
        mock_querier_class.return_value = mock_querier
        mock_version = EosVersion.from_str("4.29.3M")
        mock_querier.latest.return_value = mock_version

        # Execute
        result = search_version(
            console=MagicMock(),
            token="test-token",
            version=None,
            latest=True,
            branch="4.29",
            file_format="64",
            release_type="M",
        )

        # Assert
        assert result == "4.29.3M"
        mock_querier.latest.assert_called_once_with(
            package="eos",
            branch="4.29",
            rtype="M",
        )


class TestDownloadFiles:
    """Test suite for download_files function."""

    def test_download_files_success(self, mock_arista_dl_obj, mock_console, tmp_path):
        """Test successful file download."""
        # Setup
        output_path = tmp_path
        mock_cli = MagicMock()
        # Mock downloads to return tuple (path, was_cached)
        mock_cli.downloads.return_value = (str(output_path), False)

        # Execute
        download_files(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(output_path),
            rich_interface=True,
            debug=False,
            checksum_format="sha512sum",
        )

        # Assert - Function should execute without error
        # Check that cli.downloads() was called
        mock_cli.downloads.assert_called_once_with(
            mock_arista_dl_obj, file_path=str(output_path), rich_interface=True
        )
        mock_cli.checksum.assert_called_once_with("sha512sum")

    def test_download_files_with_custom_checksum(
        self, mock_arista_dl_obj, mock_console, tmp_path
    ):
        """Test download with custom checksum format."""
        # Setup
        output_path = tmp_path
        checksum = "sha256sum"
        mock_cli = MagicMock()
        # Mock downloads to return tuple (path, was_cached)
        mock_cli.downloads.return_value = (str(output_path), False)

        # Execute
        download_files(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(output_path),
            rich_interface=True,
            debug=False,
            checksum_format=checksum,
        )

        # Assert
        mock_cli.checksum.assert_called_once_with(checksum)

    def test_download_files_with_cached_file(
        self, mock_arista_dl_obj, mock_console, tmp_path
    ):
        """Test download with file already in cache."""
        # Setup
        output_path = tmp_path
        mock_cli = MagicMock()
        # Mock downloads to return tuple (path, was_cached=True)
        mock_cli.downloads.return_value = (str(output_path), True)

        # Execute
        download_files(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(output_path),
            rich_interface=True,
            debug=False,
            checksum_format="sha512sum",
        )

        # Assert - Should display cache message
        assert mock_console.print.call_count >= 2
        # Check that one of the calls mentions cache
        cache_message_found = False
        for call in mock_console.print.call_args_list:
            if "cache" in str(call).lower():
                cache_message_found = True
                break
        assert cache_message_found, "Cache message should be displayed"

    def test_download_files_checksum_error(
        self, mock_arista_dl_obj, mock_console, tmp_path
    ):
        """Test download with checksum verification failure."""
        # Setup
        mock_cli = MagicMock()
        # Mock downloads to return tuple (path, was_cached)
        mock_cli.downloads.return_value = (str(tmp_path), False)
        mock_cli.checksum.side_effect = subprocess.CalledProcessError(1, "checksum")

        # Execute - should exit with code 1
        with pytest.raises(SystemExit) as exc_info:
            download_files(
                console=mock_console,
                cli=mock_cli,
                arista_dl_obj=mock_arista_dl_obj,
                output=str(tmp_path),
                rich_interface=True,
                debug=False,
                checksum_format="sha512sum",
            )

        # Assert exit code is 1
        assert exc_info.value.code == 1
        # Assert that checksum was called
        mock_cli.checksum.assert_called_once_with("sha512sum")
        # Assert error message was printed
        mock_console.print.assert_called()

    def test_download_files_checksum_error_debug_mode(
        self, mock_arista_dl_obj, mock_console, tmp_path
    ):
        """Test checksum failure in debug mode prints exception details."""
        # Setup
        mock_cli = MagicMock()
        # Mock downloads to return tuple (path, was_cached)
        mock_cli.downloads.return_value = (str(tmp_path), False)
        mock_cli.checksum.side_effect = subprocess.CalledProcessError(1, "checksum")

        # Execute - should exit with code 1
        with pytest.raises(SystemExit) as exc_info:
            download_files(
                console=mock_console,
                cli=mock_cli,
                arista_dl_obj=mock_arista_dl_obj,
                output=str(tmp_path),
                rich_interface=True,
                debug=True,  # Enable debug mode
                checksum_format="sha512sum",
            )

        # Assert exit code is 1
        assert exc_info.value.code == 1
        # Assert that print_exception was called in debug mode
        mock_console.print_exception.assert_called_once_with(show_locals=True)

    def test_download_files_no_rich_interface(
        self, mock_arista_dl_obj, mock_console, tmp_path
    ):
        """Test download without Rich interface."""
        # Setup
        mock_cli = MagicMock()
        # Mock downloads to return tuple (path, was_cached)
        mock_cli.downloads.return_value = (str(tmp_path), False)

        # Execute
        download_files(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(tmp_path),
            rich_interface=False,  # Disable Rich interface
            debug=False,
            checksum_format="sha512sum",
        )

        # Assert - cli.downloads should be called with rich_interface=False
        mock_cli.downloads.assert_called_once_with(
            mock_arista_dl_obj, file_path=str(tmp_path), rich_interface=False
        )


class TestHandleDockerImport:
    """Test suite for handle_docker_import function."""

    def test_docker_import_success(self, mock_console, tmp_path):
        """Test successful Docker image import."""
        # Setup
        test_file = tmp_path / "cEOS-lab-4.29.3M.tar.xz"
        test_file.touch()
        docker_name = "arista/ceos"
        docker_tag = "4.29.3M"

        mock_cli = MagicMock()
        mock_arista_dl_obj = MagicMock()
        mock_arista_dl_obj.filename = "cEOS-lab-4.29.3M.tar.xz"
        mock_arista_dl_obj.version = "4.29.3M"

        # Execute
        result = handle_docker_import(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(tmp_path),
            docker_name=docker_name,
            docker_tag=docker_tag,
            debug=False,
        )

        # Assert
        assert result == 0
        mock_cli.import_docker.assert_called_once_with(
            local_file_path=str(tmp_path / "cEOS-lab-4.29.3M.tar.xz"),
            docker_name=docker_name,
            docker_tag=docker_tag,
            force=False,
        )

    def test_docker_import_with_default_tag(self, mock_console, tmp_path):
        """Test Docker import with default tag from version when None specified."""
        # Setup
        test_file = tmp_path / "cEOS-lab-4.29.3M.tar.xz"
        test_file.touch()
        docker_name = "arista/ceos"

        mock_cli = MagicMock()
        mock_arista_dl_obj = MagicMock()
        mock_arista_dl_obj.filename = "cEOS-lab-4.29.3M.tar.xz"
        mock_arista_dl_obj.version = "4.29.3M"

        # Execute
        result = handle_docker_import(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(tmp_path),
            docker_name=docker_name,
            docker_tag=None,  # No tag specified, should use version
            debug=False,
        )

        # Assert - should use arista_dl_obj.version as default tag
        assert result == 0
        mock_cli.import_docker.assert_called_once_with(
            local_file_path=str(tmp_path / "cEOS-lab-4.29.3M.tar.xz"),
            docker_name=docker_name,
            docker_tag="4.29.3M",  # Should use version as default tag
            force=False,
        )

    def test_docker_import_file_not_found(self, mock_console, tmp_path):
        """Test Docker import with non-existent file returns error code."""
        # Setup
        mock_cli = MagicMock()
        mock_cli.import_docker.side_effect = FileNotFoundError("File not found")
        mock_arista_dl_obj = MagicMock()
        mock_arista_dl_obj.filename = "nonexistent.tar.xz"

        # Execute
        result = handle_docker_import(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(tmp_path),
            docker_name="arista/ceos",
            docker_tag="latest",
            debug=False,
        )

        # Assert - should return 1 on FileNotFoundError
        assert result == 1

    def test_docker_import_file_not_found_debug_mode(self, mock_console, tmp_path):
        """Test file not found in debug mode prints exception details."""
        # Setup
        mock_cli = MagicMock()
        mock_cli.import_docker.side_effect = FileNotFoundError("File not found")
        mock_arista_dl_obj = MagicMock()
        mock_arista_dl_obj.filename = "nonexistent.tar.xz"

        # Execute
        result = handle_docker_import(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(tmp_path),
            docker_name="arista/ceos",
            docker_tag="latest",
            debug=True,  # Debug mode
        )

        # Assert - should return 1 and print exception details
        assert result == 1
        mock_console.print_exception.assert_called_once_with(show_locals=True)

    def test_docker_import_invalid_filename(self, mock_console, tmp_path):
        """Test Docker import with None filename returns error."""
        # Setup
        mock_cli = MagicMock()
        mock_arista_dl_obj = MagicMock()
        mock_arista_dl_obj.filename = None  # Invalid filename

        # Execute
        result = handle_docker_import(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(tmp_path),
            docker_name="arista/ceos",
            docker_tag="latest",
            debug=False,
        )

        # Assert - should return 1 for invalid filename
        assert result == 1
        # Should not call import_docker when filename is None
        mock_cli.import_docker.assert_not_called()

    def test_docker_import_with_custom_output_path(self, mock_console, tmp_path):
        """Test Docker import with custom output path."""
        # Setup
        test_file = tmp_path / "custom" / "cEOS-lab-4.29.3M.tar.xz"
        test_file.parent.mkdir(parents=True)
        test_file.touch()
        custom_output = tmp_path / "custom"

        mock_cli = MagicMock()
        mock_arista_dl_obj = MagicMock()
        mock_arista_dl_obj.filename = "cEOS-lab-4.29.3M.tar.xz"
        mock_arista_dl_obj.version = "4.29.3M"

        # Execute
        result = handle_docker_import(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_arista_dl_obj,
            output=str(custom_output),
            docker_name="arista/ceos",
            docker_tag="4.29.3M",
            debug=False,
        )

        # Assert - should use custom output path
        assert result == 0
        expected_path = str(custom_output / "cEOS-lab-4.29.3M.tar.xz")
        mock_cli.import_docker.assert_called_once_with(
            local_file_path=expected_path,
            docker_name="arista/ceos",
            docker_tag="4.29.3M",
            force=False,
        )


class TestDownloadFromContainerlabTopologyDryRun:
    """Test that dry_run skips docker import in containerlab topology download."""

    @patch("eos_downloader.cli.get.utils.SoftManager")
    @patch("eos_downloader.cli.get.utils.EosXmlObject")
    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    def test_dry_run_skips_docker_import(
        self,
        mock_extract: Mock,
        mock_eos_xml: Mock,
        mock_soft_manager: Mock,
    ) -> None:
        """When dry_run=True, handle_docker_import must not be called."""
        mock_extract.return_value = ["4.29.3M"]
        mock_eos_dl = MagicMock()
        mock_eos_xml.return_value = mock_eos_dl
        mock_cli = MagicMock()
        mock_soft_manager.return_value = mock_cli
        console = MagicMock()

        with (
            patch("eos_downloader.cli.get.utils.handle_docker_import") as mock_docker,
            patch("eos_downloader.cli.get.utils.download_files"),
        ):
            result = download_from_containerlab_topology(
                console=console,
                token="test-token",
                topology_file=Path("/fake/topo.clab.yaml"),
                image_format="cEOSarm",
                output="/tmp/out",
                docker_name="arista/ceos",
                docker_tag=None,
                dry_run=True,
                force=False,
                debug=False,
                skip_download=False,
            )

        assert result == 0
        mock_docker.assert_not_called()
        # Should print DRY-RUN message
        dry_run_printed = any(
            "DRY-RUN" in str(call) for call in console.print.call_args_list
        )
        assert dry_run_printed, "Expected a [DRY-RUN] message to be printed"

    @patch("eos_downloader.cli.get.utils.SoftManager")
    @patch("eos_downloader.cli.get.utils.EosXmlObject")
    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    @patch("eos_downloader.cli.get.utils.handle_docker_import")
    def test_no_dry_run_calls_docker_import(
        self,
        mock_docker: Mock,
        mock_extract: Mock,
        mock_eos_xml: Mock,
        mock_soft_manager: Mock,
    ) -> None:
        """When dry_run=False, handle_docker_import must be called."""
        mock_extract.return_value = ["4.29.3M"]
        mock_eos_dl = MagicMock()
        mock_eos_xml.return_value = mock_eos_dl
        mock_cli = MagicMock()
        mock_soft_manager.return_value = mock_cli
        mock_docker.return_value = 0

        with patch("eos_downloader.cli.get.utils.download_files"):
            result = download_from_containerlab_topology(
                console=MagicMock(),
                token="test-token",
                topology_file=Path("/fake/topo.clab.yaml"),
                image_format="cEOSarm",
                output="/tmp/out",
                docker_name="arista/ceos",
                docker_tag=None,
                dry_run=False,
                force=False,
                debug=False,
                skip_download=False,
            )

        assert result == 0
        mock_docker.assert_called_once()


class TestSearchVersionBranch:
    """Test branch-only search path (lines 103-104)."""

    @patch("eos_downloader.cli.get.utils.AristaXmlQuerier")
    def test_search_by_branch_without_latest(self, mock_querier_class):
        """Test searching by branch when latest=False and version=None."""
        mock_querier = MagicMock()
        mock_querier_class.return_value = mock_querier
        mock_version = EosVersion.from_str("4.29.3M")
        mock_querier.latest.return_value = mock_version

        result = search_version(
            console=MagicMock(),
            token="test-token",
            version=None,
            latest=False,
            branch="4.29",
            file_format="64",
            release_type="M",
        )

        assert result == "4.29.3M"
        mock_querier.latest.assert_called_once()

    @patch("eos_downloader.cli.get.utils.AristaXmlQuerier")
    def test_search_authentication_error(self, mock_querier_class):
        """Test AuthenticationError during search (lines 116-118)."""
        from eos_downloader.exceptions import AuthenticationError

        mock_querier = MagicMock()
        mock_querier_class.return_value = mock_querier
        mock_querier.latest.side_effect = AuthenticationError("Bad token")

        with pytest.raises(SystemExit) as exc_info:
            search_version(
                console=MagicMock(),
                token="bad-token",
                version=None,
                latest=True,
                branch=None,
                file_format="64",
                release_type="F",
            )

        assert exc_info.value.code == 1


class TestDownloadFilesValueError:
    """Test ValueError path in download_files (line 217)."""

    def test_download_files_value_error(self):
        """Test ValueError during download triggers error handling."""
        mock_cli = MagicMock()
        mock_cli.downloads.return_value = ("/tmp", False)
        mock_cli.checksum.side_effect = ValueError("Bad checksum")
        mock_console = MagicMock()
        mock_dl_obj = MagicMock()
        mock_dl_obj.version = "4.29.3M"
        mock_dl_obj.image_type = "cEOS"
        mock_dl_obj.filename = "cEOS-lab-4.29.3M.tar.xz"

        with pytest.raises(SystemExit) as exc_info:
            download_files(
                console=mock_console,
                cli=mock_cli,
                arista_dl_obj=mock_dl_obj,
                output="/tmp",
                rich_interface=True,
                debug=False,
            )

        assert exc_info.value.code == 1


class TestHandleDockerImportSuccess:
    """Test docker import success path (line 304)."""

    def test_docker_import_not_cached(self):
        """Test docker import returns 0 and was_cached=False (line 304)."""
        mock_cli = MagicMock()
        mock_cli.import_docker.return_value = False  # Not cached
        mock_console = MagicMock()
        mock_dl_obj = MagicMock()
        mock_dl_obj.filename = "cEOS-lab-4.29.3M.tar.xz"
        mock_dl_obj.version = "4.29.3M"

        result = handle_docker_import(
            console=mock_console,
            cli=mock_cli,
            arista_dl_obj=mock_dl_obj,
            output="/tmp",
            docker_name="arista/ceos",
            docker_tag="4.29.3M",
            debug=False,
        )

        assert result == 0
        # Verify "imported successfully" message
        success_msg = any(
            "imported successfully" in str(call) for call in mock_console.print.call_args_list
        )
        assert success_msg


class TestContainerlabTopologyEdgeCases:
    """Test containerlab topology edge cases."""

    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    def test_no_ceos_versions_found(self, mock_extract):
        """Test topology with no cEOS versions (lines 359-360)."""
        mock_extract.return_value = []
        console = MagicMock()

        result = download_from_containerlab_topology(
            console=console,
            token="test-token",
            topology_file=Path("/fake/topo.yaml"),
            image_format="cEOS",
            output="/tmp",
            docker_name="arista/ceos",
            docker_tag=None,
            dry_run=False,
            force=False,
            debug=False,
            skip_download=False,
        )

        assert result == 0
        no_versions_msg = any(
            "No cEOS versions" in str(call) for call in console.print.call_args_list
        )
        assert no_versions_msg

    @patch("eos_downloader.cli.get.utils.EosXmlObject")
    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    def test_eos_xml_object_exception_debug(self, mock_extract, mock_eos_xml):
        """Test EosXmlObject creation failure with debug=True (lines 376-383)."""
        mock_extract.return_value = ["4.29.3M"]
        mock_eos_xml.side_effect = Exception("XML error")
        console = MagicMock()

        result = download_from_containerlab_topology(
            console=console,
            token="test-token",
            topology_file=Path("/fake/topo.yaml"),
            image_format="cEOS",
            output="/tmp",
            docker_name="arista/ceos",
            docker_tag=None,
            dry_run=False,
            force=False,
            debug=True,
            skip_download=False,
        )

        assert result == 1
        console.print_exception.assert_called_once_with(show_locals=True)

    @patch("eos_downloader.cli.get.utils.EosXmlObject")
    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    def test_eos_xml_object_exception_no_debug(self, mock_extract, mock_eos_xml):
        """Test EosXmlObject creation failure with debug=False (lines 380-382)."""
        mock_extract.return_value = ["4.29.3M"]
        mock_eos_xml.side_effect = Exception("XML error")
        console = MagicMock()

        result = download_from_containerlab_topology(
            console=console,
            token="test-token",
            topology_file=Path("/fake/topo.yaml"),
            image_format="cEOS",
            output="/tmp",
            docker_name="arista/ceos",
            docker_tag=None,
            dry_run=False,
            force=False,
            debug=False,
            skip_download=False,
        )

        assert result == 1
        error_msg = any(
            "Failed to resolve" in str(call) for call in console.print.call_args_list
        )
        assert error_msg

    @patch("eos_downloader.cli.get.utils.download_files")
    @patch("eos_downloader.cli.get.utils.SoftManager")
    @patch("eos_downloader.cli.get.utils.EosXmlObject")
    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    def test_download_exception_debug(
        self, mock_extract, mock_eos_xml, mock_soft_manager, mock_download_files
    ):
        """Test download failure with debug=True (lines 392-399)."""
        mock_extract.return_value = ["4.29.3M"]
        mock_eos_xml.return_value = MagicMock()
        mock_soft_manager.return_value = MagicMock()
        mock_download_files.side_effect = Exception("Download failed")
        console = MagicMock()

        result = download_from_containerlab_topology(
            console=console,
            token="test-token",
            topology_file=Path("/fake/topo.yaml"),
            image_format="cEOS",
            output="/tmp",
            docker_name="arista/ceos",
            docker_tag=None,
            dry_run=False,
            force=False,
            debug=True,
            skip_download=False,
        )

        assert result == 1
        console.print_exception.assert_called_once_with(show_locals=True)

    @patch("eos_downloader.cli.get.utils.download_files")
    @patch("eos_downloader.cli.get.utils.SoftManager")
    @patch("eos_downloader.cli.get.utils.EosXmlObject")
    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    def test_download_exception_no_debug(
        self, mock_extract, mock_eos_xml, mock_soft_manager, mock_download_files
    ):
        """Test download failure with debug=False (lines 396-398)."""
        mock_extract.return_value = ["4.29.3M"]
        mock_eos_xml.return_value = MagicMock()
        mock_soft_manager.return_value = MagicMock()
        mock_download_files.side_effect = Exception("Download failed")
        console = MagicMock()

        result = download_from_containerlab_topology(
            console=console,
            token="test-token",
            topology_file=Path("/fake/topo.yaml"),
            image_format="cEOS",
            output="/tmp",
            docker_name="arista/ceos",
            docker_tag=None,
            dry_run=False,
            force=False,
            debug=False,
            skip_download=False,
        )

        assert result == 1
        error_msg = any(
            "Failed to download" in str(call) for call in console.print.call_args_list
        )
        assert error_msg

    @patch("eos_downloader.cli.get.utils.handle_docker_import")
    @patch("eos_downloader.cli.get.utils.download_files")
    @patch("eos_downloader.cli.get.utils.SoftManager")
    @patch("eos_downloader.cli.get.utils.EosXmlObject")
    @patch("eos_downloader.cli.get.utils.extract_ceos_versions")
    def test_docker_import_failure_returns_1(
        self, mock_extract, mock_eos_xml, mock_soft_manager, mock_download_files, mock_docker
    ):
        """Test failed docker import causes return code 1 (lines 411, 414-415)."""
        mock_extract.return_value = ["4.29.3M"]
        mock_eos_xml.return_value = MagicMock()
        mock_soft_manager.return_value = MagicMock()
        mock_docker.return_value = 1  # Failure

        console = MagicMock()

        result = download_from_containerlab_topology(
            console=console,
            token="test-token",
            topology_file=Path("/fake/topo.yaml"),
            image_format="cEOS",
            output="/tmp",
            docker_name="arista/ceos",
            docker_tag=None,
            dry_run=False,
            force=False,
            debug=False,
            skip_download=False,
        )

        assert result == 1
        failed_msg = any(
            "Failed versions" in str(call) for call in console.print.call_args_list
        )
        assert failed_msg
