"""Error path tests for downloader module.

Tests HTTP error handling and network error scenarios.
"""

import asyncio
import pytest
from unittest.mock import AsyncMock, patch, MagicMock

import aiohttp

from py.services.downloader import (
    Downloader,
    DownloadStalledError,
    DownloadRestartRequested,
)


class TestDownloadStreamControl:
    """Test DownloadStreamControl functionality."""

    def test_pause_clears_event(self):
        """Verify pause() clears the event."""
        from py.services.downloader import DownloadStreamControl

        control = DownloadStreamControl()
        assert control.is_set() is True  # Initially set

        control.pause()
        assert control.is_set() is False
        assert control.is_paused() is True

    def test_resume_sets_event(self):
        """Verify resume() sets the event."""
        from py.services.downloader import DownloadStreamControl

        control = DownloadStreamControl()
        control.pause()
        assert control.is_set() is False

        control.resume()
        assert control.is_set() is True
        assert control.is_paused() is False

    def test_reconnect_request_tracking(self):
        """Verify reconnect request tracking works correctly."""
        from py.services.downloader import DownloadStreamControl

        control = DownloadStreamControl()
        assert control.has_reconnect_request() is False

        control.request_reconnect()
        assert control.has_reconnect_request() is True

        # Consume the request
        consumed = control.consume_reconnect_request()
        assert consumed is True
        assert control.has_reconnect_request() is False

    def test_mark_progress_clears_reconnect(self):
        """Verify mark_progress clears reconnect requests."""
        from py.services.downloader import DownloadStreamControl

        control = DownloadStreamControl()
        control.request_reconnect()
        assert control.has_reconnect_request() is True

        control.mark_progress()
        assert control.has_reconnect_request() is False
        assert control.last_progress_timestamp is not None

    def test_time_since_last_progress(self):
        """Verify time_since_last_progress calculation."""
        from py.services.downloader import DownloadStreamControl
        import time

        control = DownloadStreamControl()

        # Initially None
        assert control.time_since_last_progress() is None

        # After marking progress
        now = time.time()
        control.mark_progress(timestamp=now)

        elapsed = control.time_since_last_progress(now=now + 5)
        assert elapsed == 5.0

    @pytest.mark.asyncio
    async def test_wait_for_resume(self):
        """Verify wait() blocks until resumed."""
        from py.services.downloader import DownloadStreamControl
        import asyncio

        control = DownloadStreamControl()
        control.pause()

        # Start a task that will wait
        wait_task = asyncio.create_task(control.wait())

        # Give it a moment to start waiting
        await asyncio.sleep(0.01)
        assert not wait_task.done()

        # Resume should unblock
        control.resume()
        await asyncio.wait_for(wait_task, timeout=0.1)


class TestDownloaderConfiguration:
    """Test downloader configuration and initialization."""

    def test_downloader_singleton_pattern(self):
        """Verify Downloader follows singleton pattern."""
        # Reset first
        Downloader._instance = None

        # Both should return same instance
        async def get_instances():
            instance1 = await Downloader.get_instance()
            instance2 = await Downloader.get_instance()
            return instance1, instance2

        import asyncio

        instance1, instance2 = asyncio.run(get_instances())

        assert instance1 is instance2

        # Cleanup
        Downloader._instance = None

    def test_default_configuration_values(self):
        """Verify default configuration values are set correctly."""
        Downloader._instance = None

        downloader = Downloader()

        assert downloader.chunk_size == 16 * 1024 * 1024  # 16MB
        assert downloader.max_retries == 5
        assert downloader.base_delay == 2.0
        assert downloader.session_timeout == 300

        # Cleanup
        Downloader._instance = None

    def test_default_headers_include_user_agent(self):
        """Verify default headers include User-Agent."""
        Downloader._instance = None

        downloader = Downloader()

        assert "User-Agent" in downloader.default_headers
        assert "ComfyUI-LoRA-Manager" in downloader.default_headers["User-Agent"]
        assert downloader.default_headers["Accept-Encoding"] == "identity"

        # Cleanup
        Downloader._instance = None

    def test_stall_timeout_resolution(self):
        """Verify stall timeout is resolved correctly."""
        Downloader._instance = None

        downloader = Downloader()
        timeout = downloader._resolve_stall_timeout()

        # Should be at least 30 seconds
        assert timeout >= 30.0

        # Cleanup
        Downloader._instance = None


class TestDownloadProgress:
    """Test DownloadProgress dataclass."""

    def test_download_progress_creation(self):
        """Verify DownloadProgress can be created with correct values."""
        from py.services.downloader import DownloadProgress
        from datetime import datetime

        progress = DownloadProgress(
            percent_complete=50.0,
            bytes_downloaded=500,
            total_bytes=1000,
            bytes_per_second=100.5,
            timestamp=datetime.now().timestamp(),
        )

        assert progress.percent_complete == 50.0
        assert progress.bytes_downloaded == 500
        assert progress.total_bytes == 1000
        assert progress.bytes_per_second == 100.5
        assert progress.timestamp is not None


class TestDownloaderExceptions:
    """Test custom exception classes."""

    def test_download_stalled_error(self):
        """Verify DownloadStalledError can be raised and caught."""
        with pytest.raises(DownloadStalledError) as exc_info:
            raise DownloadStalledError("Download stalled for 120 seconds")

        assert "stalled" in str(exc_info.value).lower()

    def test_download_restart_requested_error(self):
        """Verify DownloadRestartRequested can be raised and caught."""
        with pytest.raises(DownloadRestartRequested) as exc_info:
            raise DownloadRestartRequested("Reconnect requested after resume")

        assert (
            "reconnect" in str(exc_info.value).lower()
            or "restart" in str(exc_info.value).lower()
        )


class TestDownloaderAuthHeaders:
    """Test authentication header generation."""

    def test_get_auth_headers_without_auth(self):
        """Verify auth headers without authentication."""
        Downloader._instance = None
        downloader = Downloader()

        headers = downloader._get_auth_headers(use_auth=False)

        assert "User-Agent" in headers
        assert "Authorization" not in headers

        Downloader._instance = None

    def test_get_auth_headers_with_auth_no_api_key(self, monkeypatch):
        """Verify auth headers with auth but no API key configured."""
        Downloader._instance = None
        downloader = Downloader()

        # Mock settings manager to return no API key
        mock_settings = MagicMock()
        mock_settings.get.return_value = None

        with patch(
            "py.services.downloader.get_settings_manager", return_value=mock_settings
        ):
            headers = downloader._get_auth_headers(use_auth=True)

            # Should still have User-Agent but no Authorization
            assert "User-Agent" in headers
            assert "Authorization" not in headers

        Downloader._instance = None

    def test_get_auth_headers_with_auth_and_api_key(self, monkeypatch):
        """Verify auth headers with auth and API key configured."""
        Downloader._instance = None
        downloader = Downloader()

        # Mock settings manager to return API key
        mock_settings = MagicMock()
        mock_settings.get.return_value = "test-api-key-12345"

        with patch(
            "py.services.downloader.get_settings_manager", return_value=mock_settings
        ):
            headers = downloader._get_auth_headers(use_auth=True)

            # Should have both User-Agent and Authorization
            assert "User-Agent" in headers
            assert "Authorization" in headers
            assert "test-api-key-12345" in headers["Authorization"]
            assert headers["Content-Type"] == "application/json"

        Downloader._instance = None


class TestDownloaderSessionManagement:
    """Test session management functionality."""

    @pytest.mark.asyncio
    async def test_should_refresh_session_when_none(self):
        """Verify session refresh is needed when session is None."""
        Downloader._instance = None
        downloader = Downloader()

        # Initially should need refresh
        assert downloader._should_refresh_session() is True

        Downloader._instance = None

    def test_should_not_refresh_new_session(self):
        """Verify new session doesn't need refresh."""
        Downloader._instance = None
        downloader = Downloader()

        # Simulate a fresh session
        downloader._session_created_at = MagicMock()
        downloader._session = MagicMock()

        # Mock datetime to return current time
        from datetime import datetime, timedelta

        current_time = datetime.now()
        downloader._session_created_at = current_time

        # Should not need refresh for new session
        assert downloader._should_refresh_session() is False

        Downloader._instance = None

    def test_should_refresh_old_session(self):
        """Verify old session needs refresh."""
        Downloader._instance = None
        downloader = Downloader()

        # Simulate an old session (older than timeout)
        from datetime import datetime, timedelta

        old_time = datetime.now() - timedelta(seconds=downloader.session_timeout + 1)
        downloader._session_created_at = old_time
        downloader._session = MagicMock()

        # Should need refresh for old session
        assert downloader._should_refresh_session() is True

        Downloader._instance = None
