#!/usr/bin/python3
"""Tests for slpkg/downloader.py using pytest."""

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

import pytest
from slpkg.downloader import Downloader


@pytest.fixture
def mock_config():
    """Mock config_load for Downloader."""
    with patch("slpkg.downloader.config_load") as mock:
        mock.downloader = "wget"
        mock.maximum_parallel = 5
        mock.parallel_downloads = False
        mock.wget_options = "-q"
        mock.curl_options = "-s"
        mock.aria2_options = ""
        mock.lftp_get_options = ""
        mock.red = ""
        mock.endc = ""
        yield mock


@pytest.fixture
def downloader_inst(mock_config):
    """Fixture to create a Downloader instance."""
    with patch("slpkg.downloader.Repositories"), \
         patch("slpkg.downloader.MultiProcess"), \
         patch("slpkg.downloader.View"), \
         patch("slpkg.downloader.Utilities"), \
         patch("slpkg.downloader.Errors"):
        inst = Downloader({"option_quiet": False})
        return inst


def test_set_wget_downloader(downloader_inst):
    """Test wget command generation."""
    url = "http://example.com/file.txz"
    path = Path("/tmp")
    cmd = downloader_inst.set_wget_downloader(url, path, "file.txz")
    assert cmd[0] == "wget"
    assert f"--directory-prefix={path}" in cmd
    assert url in cmd


def test_set_curl_downloader(downloader_inst):
    """Test curl command generation."""
    downloader_inst.downloader = "curl"
    url = "http://example.com/file.txz"
    path = Path("/tmp")
    cmd = downloader_inst.set_curl_downloader(url, path, "file.txz")
    assert cmd[0] == "curl"
    assert url in cmd
    assert "--output" in cmd
    assert str(path / "file.txz") in cmd


def test_normal_download(downloader_inst):
    """Test sequential download process."""
    sources = {"repo": (["http://url1", "http://url2"], Path("/tmp"))}
    with patch.object(downloader_inst, "tools") as mock_tools:
        downloader_inst.normal_download(sources)
        assert mock_tools.call_count == 2
        mock_tools.assert_any_call("http://url1", Path("/tmp"))
        mock_tools.assert_any_call("http://url2", Path("/tmp"))


def test_tools_execution(downloader_inst):
    """Test the tools method which executes the downloader command."""
    url = "http://example.com/pkg.txz"
    path = Path("/tmp/pkg")
    
    with patch.object(downloader_inst, "check_if_downloaded") as mock_check:
        downloader_inst.tools(url, path)
        # Check if multi_process.process was called with a wget command
        downloader_inst.multi_process.process.assert_called()
        cmd = downloader_inst.multi_process.process.call_args[0][0]
        assert "wget" in cmd
        assert url in cmd
        mock_check.assert_called_once()


def test_check_if_downloaded_success(downloader_inst):
    """Test check_if_downloaded when file exists."""
    path = Path("/tmp")
    filename = "test.txz"
    repo_data = []
    
    with patch("pathlib.Path.exists", return_value=True):
        downloader_inst.check_if_downloaded(path, filename, repo_data)
        # Should not call fallback logic
        assert not downloader_inst.multi_process.process.called


def test_check_if_downloaded_fallback(downloader_inst):
    """Test fallback logic when initial download fails."""
    path = Path("/tmp/mypkg") # name is 'mypkg'
    filename = "test.src.tar.gz"
    # Mock repo data to trigger fallback (SBo repo)
    downloader_inst.repos.sbo_repo_name = "sbo"
    downloader_inst.repos.sbosrcarch_mirror = "http://fallback/"
    repo_data = ["sbo", "system"]
    
    with patch("pathlib.Path.exists", side_effect=[False, True]):
        downloader_inst.check_if_downloaded(path, filename, repo_data)
        
        # Should have called multi_process.process with fallback URL
        downloader_inst.multi_process.process.assert_called()
        fallback_cmd = downloader_inst.multi_process.process.call_args[0][0]
        assert "http://fallback/system/mypkg/test.src.tar.gz" in fallback_cmd


def test_parallel_download(downloader_inst):
    """Test parallel download threads."""
    downloader_inst.maximum_parallel = 2
    sources = {"repo": (["url1", "url2", "url3"], Path("/tmp"))}

    with patch.object(downloader_inst, "tools") as mock_tools:
        downloader_inst.parallel_download(sources)
        assert mock_tools.call_count == 3


# ---------------------------------------------------------------------------
# _is_local_url()
# ---------------------------------------------------------------------------


def test_is_local_url_file_scheme(downloader_inst) -> None:
    """file:// URI is recognised as local."""
    assert downloader_inst._is_local_url("file:///local/path/pkg.txz") is True


def test_is_local_url_absolute_path(downloader_inst) -> None:
    """Absolute filesystem path is recognised as local."""
    assert downloader_inst._is_local_url("/local/path/pkg.txz") is True


def test_is_local_url_http_not_local(downloader_inst) -> None:
    """http:// URL is not local."""
    assert downloader_inst._is_local_url("http://example.com/pkg.txz") is False


def test_is_local_url_https_not_local(downloader_inst) -> None:
    """https:// URL is not local."""
    assert downloader_inst._is_local_url("https://example.com/pkg.txz") is False


def test_is_local_url_double_slash_not_local(downloader_inst) -> None:
    """//host/path (protocol-relative) is not treated as absolute path."""
    assert downloader_inst._is_local_url("//example.com/pkg.txz") is False


# ---------------------------------------------------------------------------
# _copy_local_file()
# ---------------------------------------------------------------------------


def test_copy_local_file_file_scheme(downloader_inst, tmp_path) -> None:
    """file:// URI: source file is copied to destination directory."""
    src = tmp_path / "src" / "pkg.txz"
    src.parent.mkdir()
    src.write_bytes(b"data")
    dst_dir = tmp_path / "dst"
    dst_dir.mkdir()

    downloader_inst._copy_local_file(f"file://{src}", dst_dir)

    assert (dst_dir / "pkg.txz").exists()


def test_copy_local_file_absolute_path(downloader_inst, tmp_path) -> None:
    """Absolute path: source file is copied to destination directory."""
    src = tmp_path / "src" / "pkg.txz"
    src.parent.mkdir()
    src.write_bytes(b"data")
    dst_dir = tmp_path / "dst"
    dst_dir.mkdir()

    downloader_inst._copy_local_file(str(src), dst_dir)

    assert (dst_dir / "pkg.txz").exists()


def test_copy_local_file_not_found_calls_failure(downloader_inst, tmp_path) -> None:
    """Missing source file calls _handle_download_failure."""
    dst_dir = tmp_path / "dst"
    dst_dir.mkdir()

    with patch.object(downloader_inst, "_handle_download_failure") as mock_fail:
        downloader_inst._copy_local_file("/nonexistent/pkg.txz", dst_dir)
        mock_fail.assert_called_once_with("pkg.txz", "/nonexistent/pkg.txz")


# ---------------------------------------------------------------------------
# tools() — local-URL early-exit
# ---------------------------------------------------------------------------


def test_tools_local_url_uses_copy_not_downloader(downloader_inst, tmp_path) -> None:
    """tools() with a file:// URL calls _copy_local_file, not the downloader."""
    with patch.object(downloader_inst, "_copy_local_file") as mock_copy:
        downloader_inst.tools("file:///local/pkg.txz", tmp_path)
        mock_copy.assert_called_once_with("file:///local/pkg.txz", tmp_path)
        downloader_inst.multi_process.process.assert_not_called()


def test_tools_local_absolute_path_uses_copy(downloader_inst, tmp_path) -> None:
    """tools() with an absolute path calls _copy_local_file, not the downloader."""
    with patch.object(downloader_inst, "_copy_local_file") as mock_copy:
        downloader_inst.tools("/local/pkg.txz", tmp_path)
        mock_copy.assert_called_once_with("/local/pkg.txz", tmp_path)
        downloader_inst.multi_process.process.assert_not_called()
