"""Unit tests for UpdateService.

Tests the UpdateService business logic layer, ensuring:
- Update checking
- Update downloading
- Update installation
- Progress monitoring
- Release notes retrieval
- Error handling and error codes
"""
import pytest
from unittest.mock import AsyncMock, Mock

from app.backend.services.update_service import UpdateService
from app.backend.managers.update_manager import UpdateProgress, UpdateStatus


class TestUpdateServiceCheckForUpdates:
    """Test update checking operations."""

    @pytest.mark.asyncio
    async def test_check_for_updates_available(self, mock_update_manager):
        """Test checking for updates when update is available."""
        # Arrange
        mock_update_manager.check_for_updates.return_value = {
            "update_available": True,
            "current_version": "1.0.0",
            "latest_version": "1.1.0",
            "release_date": "2025-01-15",
            "changelog": "## New Features\n- Feature 1\n- Feature 2",
            "is_prerelease": False,
            "download_size": 10485760  # 10MB
        }
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.check_for_updates()

        # Assert
        assert result.success is True
        assert result.data["update_available"] is True
        assert result.data["latest_version"] == "1.1.0"
        assert result.data["download_size"] == 10485760

    @pytest.mark.asyncio
    async def test_check_for_updates_none_available(self, mock_update_manager):
        """Test checking for updates when system is up to date."""
        # Arrange
        mock_update_manager.check_for_updates.return_value = {
            "update_available": False,
            "current_version": "1.1.0",
            "latest_version": "1.1.0",
            "message": "System is up to date"
        }
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.check_for_updates()

        # Assert
        assert result.success is True
        assert result.data["update_available"] is False
        assert result.data["message"] == "System is up to date"

    @pytest.mark.asyncio
    async def test_check_for_updates_exception(self, mock_update_manager):
        """Test update check with exception."""
        # Arrange
        mock_update_manager.check_for_updates.side_effect = Exception("Network error")
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.check_for_updates()

        # Assert
        assert result.success is False
        assert result.error.code == "update_check_error"
        assert "Network error" in result.error.details


class TestUpdateServiceDownload:
    """Test update download operations."""

    @pytest.mark.asyncio
    async def test_download_update_success(self, mock_update_manager):
        """Test successful update download."""
        # Arrange
        mock_update_manager.download_update.return_value = True
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.download_update()

        # Assert
        assert result.success is True
        assert "successfully" in result.data["message"].lower()
        assert result.data["ready_to_install"] is True

    @pytest.mark.asyncio
    async def test_download_update_no_update_available(self, mock_update_manager):
        """Test download when no update is available."""
        # Arrange
        mock_update_manager.download_update.side_effect = ValueError("No update available to download")
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.download_update()

        # Assert
        assert result.success is False
        assert result.error.code == "no_update_available"

    @pytest.mark.asyncio
    async def test_download_update_failure(self, mock_update_manager):
        """Test download failure."""
        # Arrange
        mock_update_manager.download_update.side_effect = Exception("Download failed")
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.download_update()

        # Assert
        assert result.success is False
        assert result.error.code == "update_download_error"


class TestUpdateServiceInstall:
    """Test update installation operations."""

    @pytest.mark.asyncio
    async def test_install_update_success(self, mock_update_manager):
        """Test successful update installation."""
        # Arrange
        mock_update_manager.install_update.return_value = True
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.install_update()

        # Assert
        assert result.success is True
        assert "successfully" in result.data["message"].lower()
        assert result.data["requires_restart"] is True

    @pytest.mark.asyncio
    async def test_install_update_no_download(self, mock_update_manager):
        """Test install when no update has been downloaded."""
        # Arrange
        mock_update_manager.install_update.side_effect = ValueError("No update downloaded")
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.install_update()

        # Assert
        assert result.success is False
        assert result.error.code == "no_update_downloaded"

    @pytest.mark.asyncio
    async def test_install_update_failure(self, mock_update_manager):
        """Test installation failure."""
        # Arrange
        mock_update_manager.install_update.side_effect = Exception("Installation failed")
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.install_update()

        # Assert
        assert result.success is False
        assert result.error.code == "update_install_error"


class TestUpdateServiceProgress:
    """Test progress monitoring."""

    @pytest.mark.asyncio
    async def test_get_progress_idle(self, mock_update_manager):
        """Test getting progress when idle."""
        # Arrange
        mock_update_manager.get_progress.return_value = UpdateProgress(
            status=UpdateStatus.IDLE,
            current_version="1.0.0",
            available_version=None,
            download_progress=0.0,
            error_message=None,
            last_check="2025-01-15T10:00:00"
        )
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.get_progress()

        # Assert
        assert result.success is True
        assert result.data["status"] == "idle"
        assert result.data["current_version"] == "1.0.0"
        assert result.data["download_progress"] == 0.0

    @pytest.mark.asyncio
    async def test_get_progress_downloading(self, mock_update_manager):
        """Test getting progress during download."""
        # Arrange
        mock_update_manager.get_progress.return_value = UpdateProgress(
            status=UpdateStatus.DOWNLOADING,
            current_version="1.0.0",
            available_version="1.1.0",
            download_progress=45.5,
            error_message=None,
            last_check="2025-01-15T10:00:00"
        )
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.get_progress()

        # Assert
        assert result.success is True
        assert result.data["status"] == "downloading"
        assert result.data["available_version"] == "1.1.0"
        assert result.data["download_progress"] == 45.5

    @pytest.mark.asyncio
    async def test_get_progress_failed(self, mock_update_manager):
        """Test getting progress after failure."""
        # Arrange
        mock_update_manager.get_progress.return_value = UpdateProgress(
            status=UpdateStatus.FAILED,
            current_version="1.0.0",
            available_version="1.1.0",
            download_progress=0.0,
            error_message="Network timeout",
            last_check="2025-01-15T10:00:00"
        )
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.get_progress()

        # Assert
        assert result.success is True
        assert result.data["status"] == "failed"
        assert result.data["error_message"] == "Network timeout"


class TestUpdateServiceReleaseNotes:
    """Test release notes retrieval."""

    @pytest.mark.asyncio
    async def test_get_release_notes_latest(self, mock_update_manager):
        """Test getting latest release notes."""
        # Arrange
        mock_update_manager.get_release_notes.return_value = "## Version 1.1.0\n- New feature"
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.get_release_notes(version=None)

        # Assert
        assert result.success is True
        assert result.data["version"] == "latest"
        assert "New feature" in result.data["release_notes"]

    @pytest.mark.asyncio
    async def test_get_release_notes_specific_version(self, mock_update_manager):
        """Test getting notes for specific version."""
        # Arrange
        mock_update_manager.get_release_notes.return_value = "## Version 1.0.0\n- Initial release"
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.get_release_notes(version="1.0.0")

        # Assert
        assert result.success is True
        assert result.data["version"] == "1.0.0"
        assert "Initial release" in result.data["release_notes"]
        mock_update_manager.get_release_notes.assert_called_once_with("1.0.0")


class TestUpdateServiceCombinedOperations:
    """Test combined update operations."""

    @pytest.mark.asyncio
    async def test_check_and_download_update_available(self, mock_update_manager):
        """Test combined check and download when update is available."""
        # Arrange
        mock_update_manager.check_for_updates.return_value = {
            "update_available": True,
            "current_version": "1.0.0",
            "latest_version": "1.1.0"
        }
        mock_update_manager.download_update.return_value = True
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.check_and_download_update()

        # Assert
        assert result.success is True
        assert result.data["ready_to_install"] is True
        assert "update_info" in result.data
        mock_update_manager.download_update.assert_called_once()

    @pytest.mark.asyncio
    async def test_check_and_download_update_not_available(self, mock_update_manager):
        """Test combined check and download when no update available."""
        # Arrange
        mock_update_manager.check_for_updates.return_value = {
            "update_available": False,
            "current_version": "1.1.0"
        }
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.check_and_download_update()

        # Assert
        assert result.success is True
        assert result.data["update_available"] is False
        mock_update_manager.download_update.assert_not_called()

    @pytest.mark.asyncio
    async def test_full_update_success(self, mock_update_manager):
        """Test full update workflow."""
        # Arrange
        mock_update_manager.check_for_updates.return_value = {
            "update_available": True,
            "current_version": "1.0.0",
            "latest_version": "1.1.0"
        }
        mock_update_manager.download_update.return_value = True
        mock_update_manager.install_update.return_value = True
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.full_update()

        # Assert
        assert result.success is True
        assert result.data["update_performed"] is True
        assert result.data["previous_version"] == "1.0.0"
        assert result.data["new_version"] == "1.1.0"
        assert result.data["requires_restart"] is True

        # Verify all steps were called
        mock_update_manager.check_for_updates.assert_called_once()
        mock_update_manager.download_update.assert_called_once()
        mock_update_manager.install_update.assert_called_once()

    @pytest.mark.asyncio
    async def test_full_update_already_up_to_date(self, mock_update_manager):
        """Test full update when already up to date."""
        # Arrange
        mock_update_manager.check_for_updates.return_value = {
            "update_available": False,
            "current_version": "1.1.0"
        }
        service = UpdateService(mock_update_manager)

        # Act
        result = await service.full_update()

        # Assert
        assert result.success is True
        assert result.data["update_performed"] is False
        mock_update_manager.download_update.assert_not_called()
        mock_update_manager.install_update.assert_not_called()
