"""Integration tests for CLI workflows.

Tests end-to-end CLI command execution using subprocess-style invocation of
the nfo_cli main() function with mocked services.
"""

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

import pytest

from src.cli.nfo_cli import main, scan_and_create_nfo, update_nfo_files


def _mock_serie(name: str, has_nfo: bool = False):
    """Create a mock serie object."""
    s = MagicMock()
    s.name = name
    s.folder = name
    s.has_nfo.return_value = has_nfo
    s.has_poster.return_value = False
    s.has_logo.return_value = False
    s.has_fanart.return_value = False
    return s


class TestScanWorkflow:
    """End-to-end scan workflow."""

    @pytest.mark.asyncio
    @patch("src.cli.nfo_cli.SeriesManagerService")
    @patch("src.cli.nfo_cli.settings")
    async def test_scan_creates_nfo_and_closes(self, mock_settings, mock_sms):
        """Full scan workflow: init → scan → close."""
        mock_settings.tmdb_api_key = "key"
        mock_settings.anime_directory = "/anime"
        mock_settings.nfo_auto_create = True
        mock_settings.nfo_update_on_scan = False
        mock_settings.nfo_download_poster = False
        mock_settings.nfo_download_logo = False
        mock_settings.nfo_download_fanart = False

        series = [_mock_serie("Naruto"), _mock_serie("Bleach")]
        mock_serie_list = MagicMock()
        mock_serie_list.get_all.return_value = series
        mock_serie_list.load_series = MagicMock()

        manager = MagicMock()
        manager.get_serie_list.return_value = mock_serie_list
        manager.scan_and_process_nfo = AsyncMock()
        manager.close = AsyncMock()
        mock_sms.from_settings.return_value = manager

        result = await scan_and_create_nfo()

        assert result == 0
        manager.scan_and_process_nfo.assert_awaited_once()
        manager.close.assert_awaited_once()

    @pytest.mark.asyncio
    @patch("src.cli.nfo_cli.SeriesManagerService")
    @patch("src.cli.nfo_cli.settings")
    async def test_scan_all_have_nfo_and_no_update(self, mock_settings, mock_sms):
        """When all series have NFO and update_on_scan is False, returns 0 early."""
        mock_settings.tmdb_api_key = "key"
        mock_settings.anime_directory = "/anime"
        mock_settings.nfo_auto_create = True
        mock_settings.nfo_update_on_scan = False
        mock_settings.nfo_download_poster = False
        mock_settings.nfo_download_logo = False
        mock_settings.nfo_download_fanart = False

        series = [_mock_serie("Naruto", has_nfo=True)]
        mock_serie_list = MagicMock()
        mock_serie_list.get_all.return_value = series

        manager = MagicMock()
        manager.get_serie_list.return_value = mock_serie_list
        mock_sms.from_settings.return_value = manager

        result = await scan_and_create_nfo()
        assert result == 0


class TestUpdateWorkflow:
    """End-to-end update workflow."""

    @pytest.mark.asyncio
    @patch("src.cli.nfo_cli.asyncio")
    @patch("src.cli.nfo_cli.settings")
    async def test_update_processes_each_serie(self, mock_settings, mock_sleeper):
        """Update calls update_tvshow_nfo for every serie with NFO."""
        mock_settings.tmdb_api_key = "key"
        mock_settings.anime_directory = "/anime"
        mock_settings.nfo_download_poster = True
        mock_settings.nfo_download_logo = False
        mock_settings.nfo_download_fanart = False
        mock_sleeper.sleep = AsyncMock()

        series = [
            _mock_serie("A", has_nfo=True),
            _mock_serie("B", has_nfo=True),
            _mock_serie("C", has_nfo=False),
        ]

        nfo_svc = MagicMock()
        nfo_svc.update_tvshow_nfo = AsyncMock()
        nfo_svc.close = AsyncMock()

        with patch("src.core.entities.SerieList.SerieList") as mock_sl:
            mock_list = MagicMock()
            mock_list.get_all.return_value = series
            mock_sl.return_value = mock_list
            with patch(
                "src.core.services.nfo_factory.create_nfo_service",
                return_value=nfo_svc,
            ):
                result = await update_nfo_files()

        assert result == 0
        # Only A and B have NFO
        assert nfo_svc.update_tvshow_nfo.await_count == 2
        nfo_svc.close.assert_awaited_once()

    @pytest.mark.asyncio
    @patch("src.cli.nfo_cli.asyncio")
    @patch("src.cli.nfo_cli.settings")
    async def test_update_continues_on_per_series_error(
        self, mock_settings, mock_sleeper
    ):
        """An error updating one serie doesn't stop others."""
        mock_settings.tmdb_api_key = "key"
        mock_settings.anime_directory = "/anime"
        mock_settings.nfo_download_poster = False
        mock_settings.nfo_download_logo = False
        mock_settings.nfo_download_fanart = False
        mock_sleeper.sleep = AsyncMock()

        series = [
            _mock_serie("OK", has_nfo=True),
            _mock_serie("Fail", has_nfo=True),
        ]

        nfo_svc = MagicMock()
        # First call succeeds, second raises
        nfo_svc.update_tvshow_nfo = AsyncMock(
            side_effect=[None, RuntimeError("api down")]
        )
        nfo_svc.close = AsyncMock()

        with patch("src.core.entities.SerieList.SerieList") as mock_sl:
            mock_list = MagicMock()
            mock_list.get_all.return_value = series
            mock_sl.return_value = mock_list
            with patch(
                "src.core.services.nfo_factory.create_nfo_service",
                return_value=nfo_svc,
            ):
                result = await update_nfo_files()

        assert result == 0
        assert nfo_svc.update_tvshow_nfo.await_count == 2


class TestErrorHandlingWorkflows:
    """Test error paths in CLI workflows."""

    @patch("src.cli.nfo_cli.sys")
    def test_main_usage_printed_on_no_args(self, mock_sys, capsys):
        """Shows usage and returns 1 with no args."""
        mock_sys.argv = ["nfo_cli"]
        result = main()
        assert result == 1

    @pytest.mark.asyncio
    @patch("src.cli.nfo_cli.settings")
    async def test_missing_config_returns_1(self, mock_settings):
        """Missing required settings yields exit code 1."""
        mock_settings.tmdb_api_key = None
        assert await scan_and_create_nfo() == 1

        mock_settings.tmdb_api_key = "key"
        mock_settings.anime_directory = None
        assert await scan_and_create_nfo() == 1
