"""Integration tests for the complete update flow."""

import asyncio
import json
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from webdrop_bridge.config import Config
from webdrop_bridge.core.updater import Release, UpdateManager


@pytest.fixture
def config(tmp_path):
    """Create test config."""
    return Config(
        app_name="Test WebDrop",
        app_version="0.0.1",
        log_level="INFO",
        log_file=None,
        allowed_roots=[tmp_path],
        allowed_urls=[],
        webapp_url="file:///./webapp/index.html",
        window_width=800,
        window_height=600,
        window_title="Test WebDrop v0.0.1",
        enable_logging=False,
    )


@pytest.fixture
def mock_forgejo_response():
    """Mock Forgejo API response - formatted as returned by _fetch_release."""
    return {
        "tag_name": "v0.0.2",
        "name": "WebDropBridge v0.0.2",
        "version": "0.0.2",  # _fetch_release adds this
        "body": "## Bug Fixes\n- Fixed drag and drop on macOS",
        "assets": [
            {
                "name": "WebDropBridge.exe",
                "browser_download_url": "https://git.him-tools.de/HIM-public/webdrop-bridge/releases/download/v0.0.2/WebDropBridge.exe",
            },
            {
                "name": "WebDropBridge.exe.sha256",
                "browser_download_url": "https://git.him-tools.de/HIM-public/webdrop-bridge/releases/download/v0.0.2/WebDropBridge.exe.sha256",
            },
        ],
        "published_at": "2026-01-29T10:00:00Z",
    }


class TestUpdateFlowIntegration:
    """Integration tests for the complete update check flow."""

    @pytest.mark.asyncio
    async def test_full_update_check_flow(self, config, mock_forgejo_response, tmp_path):
        """Test complete flow: API query -> version check -> signal."""
        manager = UpdateManager(
            current_version=config.app_version,
            config_dir=tmp_path
        )

        # Mock the API fetch
        with patch.object(manager, "_fetch_release") as mock_fetch:
            mock_fetch.return_value = mock_forgejo_response

            # Run check
            release = await manager.check_for_updates()

            # Verify API was called
            mock_fetch.assert_called_once()

            # Verify we got a release
            assert release is not None
            assert release.version == "0.0.2"
            assert release.tag_name == "v0.0.2"
            assert len(release.assets) == 2

    @pytest.mark.asyncio
    async def test_update_check_with_cache(self, config, mock_forgejo_response, tmp_path):
        """Test that cache is used on second call."""
        manager = UpdateManager(
            current_version=config.app_version,
            config_dir=tmp_path
        )

        with patch.object(manager, "_fetch_release") as mock_fetch:
            mock_fetch.return_value = mock_forgejo_response

            # First call - should fetch from API
            release1 = await manager.check_for_updates()
            assert mock_fetch.call_count == 1

            # Second call - should use cache
            release2 = await manager.check_for_updates()
            assert mock_fetch.call_count == 1  # Still 1, cache used

            # Verify both got same result
            assert release1 is not None
            assert release2 is not None
            assert release1.version == release2.version

    @pytest.mark.asyncio
    async def test_update_check_no_newer_version(self, config, tmp_path):
        """Test that no update available when latest is same version."""
        manager = UpdateManager(
            current_version="0.0.2",
            config_dir=tmp_path
        )

        response = {
            "tag_name": "v0.0.2",
            "name": "WebDropBridge v0.0.2",
            "body": "",
            "assets": [],
            "published_at": "2026-01-29T10:00:00Z",
        }

        with patch.object(manager, "_fetch_release") as mock_fetch:
            mock_fetch.return_value = response

            release = await manager.check_for_updates()

            # Should return None since version is not newer
            assert release is None

    @pytest.mark.asyncio
    async def test_update_check_network_error(self, config, tmp_path):
        """Test graceful handling of network errors."""
        manager = UpdateManager(
            current_version=config.app_version,
            config_dir=tmp_path
        )

        # Mock network error
        with patch.object(manager, "_fetch_release") as mock_fetch:
            mock_fetch.side_effect = Exception("Connection timeout")

            release = await manager.check_for_updates()

            # Should return None on error
            assert release is None

    @pytest.mark.asyncio
    async def test_version_parsing_in_api_response(self, config, tmp_path):
        """Test that version is correctly extracted from tag_name."""
        manager = UpdateManager(
            current_version=config.app_version,
            config_dir=tmp_path
        )

        # API returns version with 'v' prefix - but _fetch_release processes it
        response = {
            "tag_name": "v1.2.3",
            "name": "Release",
            "version": "1.2.3",  # _fetch_release adds this
            "body": "",
            "assets": [],
            "published_at": "2026-01-29T10:00:00Z",
        }

        with patch.object(manager, "_fetch_release") as mock_fetch:
            mock_fetch.return_value = response

            release = await manager.check_for_updates()

            # Version should be extracted correctly (without 'v')
            assert release is not None
            assert release.version == "1.2.3"

    @pytest.mark.asyncio
    async def test_asset_parsing_in_release(self, config, mock_forgejo_response, tmp_path):
        """Test that release assets are correctly parsed."""
        manager = UpdateManager(
            current_version=config.app_version,
            config_dir=tmp_path
        )

        with patch.object(manager, "_fetch_release") as mock_fetch:
            mock_fetch.return_value = mock_forgejo_response

            release = await manager.check_for_updates()

            # Should have both exe and checksum
            assert release is not None
            assert len(release.assets) == 2
            asset_names = [a["name"] for a in release.assets]
            assert "WebDropBridge.exe" in asset_names
            assert "WebDropBridge.exe.sha256" in asset_names

    @pytest.mark.asyncio
    async def test_changelog_preserved(self, config, mock_forgejo_response, tmp_path):
        """Test that release notes/changelog are preserved."""
        manager = UpdateManager(
            current_version=config.app_version,
            config_dir=tmp_path
        )

        with patch.object(manager, "_fetch_release") as mock_fetch:
            mock_fetch.return_value = mock_forgejo_response

            release = await manager.check_for_updates()

            # Changelog should be available
            assert release is not None
            assert release.body == mock_forgejo_response["body"]
            assert "Bug Fixes" in release.body
