"""
Unit tests for DownloadManager
"""
import pytest
import asyncio
from pathlib import Path
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime

from app.download_manager import DownloadManager
from app.models import DownloadTask, DownloadStatus, DownloadRequest, HostType


class TestDownloadManagerInit:
    """Tests for DownloadManager initialization"""

    def test_init_default_parameters(self, temp_download_dir):
        """Test DownloadManager initialization with default parameters"""
        manager = DownloadManager(download_dir=str(temp_download_dir))
        assert manager.download_dir == temp_download_dir
        assert manager.max_parallel == 3
        assert manager.tasks == {}
        assert manager.active_downloads == {}
        assert temp_download_dir.exists()

    def test_init_custom_parameters(self, temp_download_dir):
        """Test DownloadManager initialization with custom parameters"""
        manager = DownloadManager(
            download_dir=str(temp_download_dir),
            max_parallel=5
        )
        assert manager.max_parallel == 5

    def test_init_creates_download_directory(self, temp_dir):
        """Test that initialization creates download directory"""
        download_dir = temp_dir / "new_downloads"
        assert not download_dir.exists()

        manager = DownloadManager(download_dir=str(download_dir))
        assert download_dir.exists()
        assert download_dir.is_dir()


class TestDownloadManagerTaskCreation:
    """Tests for task creation and management"""

    def test_create_task(self, download_manager, sample_download_request):
        """Test creating a new download task"""
        task = download_manager.create_task(sample_download_request)

        assert isinstance(task, DownloadTask)
        assert task.url == sample_download_request.url
        assert task.filename == sample_download_request.filename
        assert task.status == DownloadStatus.PENDING
        assert task.id in download_manager.tasks

    def test_create_task_with_custom_filename(self, download_manager):
        """Test creating task with custom filename"""
        request = DownloadRequest(
            url="https://example.com/file.mp4",
            filename="custom_name.mp4"
        )
        task = download_manager.create_task(request)
        assert task.filename == "custom_name.mp4"

    def test_create_task_without_filename(self, download_manager):
        """Test creating task without filename uses default"""
        request = DownloadRequest(url="https://example.com/file.mp4")
        task = download_manager.create_task(request)
        assert task.filename == "download"

    def test_create_multiple_tasks(self, download_manager):
        """Test creating multiple tasks"""
        request1 = DownloadRequest(url="https://example.com/file1.mp4")
        request2 = DownloadRequest(url="https://example.com/file2.mp4")

        task1 = download_manager.create_task(request1)
        task2 = download_manager.create_task(request2)

        assert task1.id != task2.id
        assert len(download_manager.tasks) == 2

    def test_get_task(self, download_manager, sample_download_request):
        """Test retrieving a task by ID"""
        task = download_manager.create_task(sample_download_request)
        retrieved_task = download_manager.get_task(task.id)

        assert retrieved_task is not None
        assert retrieved_task.id == task.id
        assert retrieved_task.url == task.url

    def test_get_task_nonexistent(self, download_manager):
        """Test retrieving a non-existent task"""
        task = download_manager.get_task("nonexistent-id")
        assert task is None

    def test_get_all_tasks(self, download_manager):
        """Test retrieving all tasks"""
        # Create multiple tasks
        for i in range(3):
            request = DownloadRequest(url=f"https://example.com/file{i}.mp4")
            download_manager.create_task(request)

        all_tasks = download_manager.get_all_tasks()
        assert len(all_tasks) == 3
        assert all(isinstance(task, DownloadTask) for task in all_tasks)

    def test_get_all_tasks_empty(self, download_manager):
        """Test retrieving all tasks when none exist"""
        all_tasks = download_manager.get_all_tasks()
        assert all_tasks == []


class TestDownloadManagerStartDownload:
    """Tests for starting downloads"""

    @pytest.mark.asyncio
    async def test_start_download_success(self, download_manager, sample_download_request):
        """Test starting a download successfully"""
        task = download_manager.create_task(sample_download_request)

        # Mock the actual download process
        with patch('app.download_manager.get_downloader') as mock_get_downloader:
            mock_downloader = AsyncMock()
            mock_downloader.get_download_link.return_value = (
                "https://example.com/direct_link",
                "video.mp4"
            )
            mock_get_downloader.return_value = mock_downloader

            # Mock httpx client
            with patch('httpx.AsyncClient') as mock_client_class:
                mock_client = AsyncMock()
                mock_response = AsyncMock()
                mock_response.status_code = 200
                mock_response.headers = {"content-length": "1000"}
                mock_response.raise_for_status = Mock()

                # Mock streaming
                async def mock_aiter_bytes(chunk_size):
                    yield b"x" * 1000  # Single chunk

                mock_response.aiter_bytes = mock_aiter_bytes
                mock_client.stream.return_value.__aenter__.return_value = mock_response
                mock_client_class.return_value = mock_client

                await download_manager.start_download(task.id)

                # Give it a moment to start
                await asyncio.sleep(0.1)

    @pytest.mark.asyncio
    async def test_start_download_nonexistent_task(self, download_manager):
        """Test starting download for non-existent task"""
        with pytest.raises(ValueError, match="not found"):
            await download_manager.start_download("nonexistent-id")

    @pytest.mark.asyncio
    async def test_start_download_already_downloading(self, download_manager, sample_download_request):
        """Test starting a task that's already downloading"""
        task = download_manager.create_task(sample_download_request)
        task.status = DownloadStatus.DOWNLOADING

        # Should not raise an error, just return
        await download_manager.start_download(task.id)


class TestDownloadManagerPauseDownload:
    """Tests for pausing downloads"""

    @pytest.mark.asyncio
    async def test_pause_download(self, download_manager):
        """Test pausing an active download"""
        request = DownloadRequest(url="https://example.com/file.mp4")
        task = download_manager.create_task(request)
        task.status = DownloadStatus.DOWNLOADING

        # Create a fake active download
        async def fake_download():
            await asyncio.sleep(10)

        download_manager.active_downloads[task.id] = asyncio.create_task(fake_download())

        await download_manager.pause_download(task.id)

        assert task.status == DownloadStatus.PAUSED
        assert task.id not in download_manager.active_downloads

    @pytest.mark.asyncio
    async def test_pause_download_not_downloading(self, download_manager):
        """Test pausing a task that's not downloading"""
        request = DownloadRequest(url="https://example.com/file.mp4")
        task = download_manager.create_task(request)
        task.status = DownloadStatus.PENDING

        # Should not crash
        await download_manager.pause_download(task.id)
        assert task.status == DownloadStatus.PENDING

    @pytest.mark.asyncio
    async def test_pause_nonexistent_task(self, download_manager):
        """Test pausing a non-existent task"""
        # Should not crash
        await download_manager.pause_download("nonexistent-id")


class TestDownloadManagerCancelDownload:
    """Tests for canceling downloads"""

    @pytest.mark.asyncio
    async def test_cancel_download(self, download_manager, temp_download_dir):
        """Test canceling a download"""
        request = DownloadRequest(url="https://example.com/file.mp4")
        task = download_manager.create_task(request)
        task.status = DownloadStatus.DOWNLOADING

        # Create a fake file
        file_path = temp_download_dir / "test_file.mp4"
        file_path.write_text("test content")
        task.file_path = str(file_path)

        await download_manager.cancel_download(task.id)

        assert task.status == DownloadStatus.CANCELLED
        assert not file_path.exists()

    @pytest.mark.asyncio
    async def test_cancel_download_with_active_task(self, download_manager):
        """Test canceling a download with an active task"""
        request = DownloadRequest(url="https://example.com/file.mp4")
        task = download_manager.create_task(request)
        task.status = DownloadStatus.DOWNLOADING

        # Create a fake active download
        async def fake_download():
            try:
                await asyncio.sleep(10)
            except asyncio.CancelledError:
                raise

        fake_task = asyncio.create_task(fake_download())
        download_manager.active_downloads[task.id] = fake_task

        await download_manager.cancel_download(task.id)

        assert task.status == DownloadStatus.CANCELLED
        assert task.id not in download_manager.active_downloads

    @pytest.mark.asyncio
    async def test_cancel_nonexistent_task(self, download_manager):
        """Test canceling a non-existent task"""
        # Should not crash
        await download_manager.cancel_download("nonexistent-id")


class TestDownloadManagerConcurrency:
    """Tests for concurrent download management"""

    def test_semaphore_limit(self, download_manager):
        """Test that semaphore respects max_parallel limit"""
        manager = DownloadManager(max_parallel=2)
        assert manager._semaphore._value == 2

    @pytest.mark.asyncio
    async def test_parallel_downloads_respect_limit(self, download_manager):
        """Test that parallel downloads respect the limit"""
        download_count = 0
        max_concurrent = 0

        async def mock_download():
            nonlocal download_count, max_concurrent
            download_count += 1
            max_concurrent = max(max_concurrent, download_count)
            await asyncio.sleep(0.1)
            download_count -= 1

        # Start more downloads than max_parallel
        tasks = []
        for _ in range(5):
            task = asyncio.create_task(mock_download())
            tasks.append(task)

        await asyncio.gather(*tasks)

        # With proper semaphore, should not exceed max_parallel
        # Note: This is a basic test, real concurrency testing needs more setup


class TestDownloadManagerProgressTracking:
    """Tests for progress tracking during downloads"""

    def test_task_progress_initialization(self, download_manager):
        """Test that task initializes with correct progress values"""
        request = DownloadRequest(url="https://example.com/file.mp4")
        task = download_manager.create_task(request)

        assert task.progress == 0.0
        assert task.downloaded_bytes == 0
        assert task.speed == 0.0
        assert task.total_bytes is None

    def test_task_timestamps(self, download_manager):
        """Test that task timestamps are set correctly"""
        request = DownloadRequest(url="https://example.com/file.mp4")
        task = download_manager.create_task(request)

        assert task.created_at is not None
        assert isinstance(task.created_at, datetime)
        assert task.started_at is None
        assert task.completed_at is None


class TestDownloadManagerFileHandling:
    """Tests for file handling during downloads"""

    def test_file_path_assignment(self, download_manager, temp_download_dir):
        """Test that file paths are assigned correctly"""
        request = DownloadRequest(
            url="https://example.com/video.mp4",
            filename="test_video.mp4"
        )
        task = download_manager.create_task(request)

        expected_path = str(temp_download_dir / "test_video.mp4")
        # File path is set during download, not creation
        assert task.file_path is None or task.file_path == expected_path

    def test_download_dir_persistence(self, download_manager):
        """Test that download directory persists across operations"""
        assert download_manager.download_dir.exists()
        assert download_manager.download_dir.is_dir()


class TestDownloadManagerErrorHandling:
    """Tests for error handling"""

    @pytest.mark.asyncio
    async def test_download_error_sets_failed_status(self):
        """Test that download errors set task to failed status"""
        temp_dir = Path(__file__).parent / "temp_test_downloads"
        temp_dir.mkdir(exist_ok=True)

        try:
            manager = DownloadManager(download_dir=str(temp_dir))
            request = DownloadRequest(url="https://invalid-url-that-will-fail.com/file.mp4")
            task = manager.create_task(request)

            # Mock get_downloader to raise an error
            with patch('app.download_manager.get_downloader') as mock_get_downloader:
                mock_downloader = AsyncMock()
                mock_downloader.get_download_link.side_effect = Exception("Download failed")
                mock_get_downloader.return_value = mock_downloader

                try:
                    await manager.start_download(task.id)
                    await asyncio.sleep(0.1)  # Give it time to process
                except:
                    pass

                # The task should be in tasks dict
                if task.id in manager.tasks:
                    assert manager.tasks[task.id].status == DownloadStatus.FAILED
                    assert manager.tasks[task.id].error is not None
        finally:
            # Cleanup
            import shutil
            shutil.rmtree(temp_dir, ignore_errors=True)


class TestDownloadManagerEdgeCases:
    """Tests for edge cases and boundary conditions"""

    def test_create_task_with_special_chars_in_filename(self, download_manager):
        """Test creating task with special characters in filename"""
        request = DownloadRequest(
            url="https://example.com/file.mp4",
            filename="test file [2023] - épisode 1.mp4"
        )
        task = download_manager.create_task(request)
        assert "[" in task.filename
        assert "]" in task.filename

    def test_concurrent_task_creation(self, download_manager):
        """Test creating tasks concurrently"""
        async def create_tasks():
            tasks = []
            for i in range(10):
                request = DownloadRequest(url=f"https://example.com/file{i}.mp4")
                task = download_manager.create_task(request)
                tasks.append(task)
            return tasks

        tasks = asyncio.run(create_tasks())
        assert len(tasks) == 10
        assert len(download_manager.tasks) == 10
