"""Unit tests for base_provider.py - Abstract base class and interface contracts."""

from abc import ABC
from typing import Any, Dict, List
from unittest.mock import MagicMock

import pytest

from src.core.providers.base_provider import Loader


class TestLoaderAbstractInterface:
    """Test that Loader defines the correct abstract interface."""

    def test_loader_is_abstract_class(self):
        """Loader should be an abstract class and not directly instantiable."""
        assert issubclass(Loader, ABC)
        with pytest.raises(TypeError):
            Loader()

    def test_loader_defines_search_method(self):
        """Loader must define abstract search method."""
        assert hasattr(Loader, "search")
        assert getattr(Loader.search, "__isabstractmethod__", False)

    def test_loader_defines_is_language_method(self):
        """Loader must define abstract is_language method."""
        assert hasattr(Loader, "is_language")
        assert getattr(Loader.is_language, "__isabstractmethod__", False)

    def test_loader_defines_download_method(self):
        """Loader must define abstract download method."""
        assert hasattr(Loader, "download")
        assert getattr(Loader.download, "__isabstractmethod__", False)

    def test_loader_defines_get_site_key_method(self):
        """Loader must define abstract get_site_key method."""
        assert hasattr(Loader, "get_site_key")
        assert getattr(Loader.get_site_key, "__isabstractmethod__", False)

    def test_loader_defines_get_title_method(self):
        """Loader must define abstract get_title method."""
        assert hasattr(Loader, "get_title")
        assert getattr(Loader.get_title, "__isabstractmethod__", False)

    def test_loader_defines_get_season_episode_count_method(self):
        """Loader must define abstract get_season_episode_count method."""
        assert hasattr(Loader, "get_season_episode_count")
        assert getattr(
            Loader.get_season_episode_count, "__isabstractmethod__", False
        )

    def test_loader_defines_subscribe_download_progress(self):
        """Loader must define abstract subscribe_download_progress method."""
        assert hasattr(Loader, "subscribe_download_progress")
        assert getattr(
            Loader.subscribe_download_progress, "__isabstractmethod__", False
        )

    def test_loader_defines_unsubscribe_download_progress(self):
        """Loader must define abstract unsubscribe_download_progress method."""
        assert hasattr(Loader, "unsubscribe_download_progress")
        assert getattr(
            Loader.unsubscribe_download_progress, "__isabstractmethod__", False
        )


class ConcreteLoader(Loader):
    """Minimal concrete implementation for testing inheritance."""

    def subscribe_download_progress(self, handler):
        pass

    def unsubscribe_download_progress(self, handler):
        pass

    def search(self, word: str) -> List[Dict[str, Any]]:
        return [{"title": word}]

    def is_language(
        self,
        season: int,
        episode: int,
        key: str,
        language: str = "German Dub",
    ) -> bool:
        return True

    def download(
        self,
        base_directory: str,
        serie_folder: str,
        season: int,
        episode: int,
        key: str,
        language: str = "German Dub",
    ) -> bool:
        return True

    def get_site_key(self) -> str:
        return "test.provider"

    def get_title(self, key: str) -> str:
        return f"Title for {key}"

    def get_season_episode_count(self, slug: str) -> Dict[int, int]:
        return {1: 12, 2: 24}


class TestLoaderInheritance:
    """Test concrete implementations of the Loader interface."""

    def test_concrete_loader_can_be_instantiated(self):
        """A fully implemented subclass should instantiate without error."""
        loader = ConcreteLoader()
        assert isinstance(loader, Loader)

    def test_concrete_loader_search(self):
        """Concrete search should return a list of dicts."""
        loader = ConcreteLoader()
        result = loader.search("Naruto")
        assert isinstance(result, list)
        assert result[0]["title"] == "Naruto"

    def test_concrete_loader_is_language(self):
        """Concrete is_language should return bool."""
        loader = ConcreteLoader()
        assert loader.is_language(1, 1, "naruto") is True

    def test_concrete_loader_is_language_default_param(self):
        """is_language should default to 'German Dub' language."""
        loader = ConcreteLoader()
        result = loader.is_language(1, 1, "naruto")
        assert isinstance(result, bool)

    def test_concrete_loader_download(self):
        """Concrete download should return bool."""
        loader = ConcreteLoader()
        result = loader.download("/base", "folder", 1, 1, "naruto")
        assert result is True

    def test_concrete_loader_get_site_key(self):
        """Concrete get_site_key should return a string."""
        loader = ConcreteLoader()
        assert loader.get_site_key() == "test.provider"

    def test_concrete_loader_get_title(self):
        """Concrete get_title should return title string."""
        loader = ConcreteLoader()
        assert loader.get_title("naruto") == "Title for naruto"

    def test_concrete_loader_get_season_episode_count(self):
        """Concrete get_season_episode_count should return dict."""
        loader = ConcreteLoader()
        result = loader.get_season_episode_count("naruto")
        assert isinstance(result, dict)
        assert result[1] == 12
        assert result[2] == 24

    def test_concrete_loader_subscribe_download_progress(self):
        """subscribe_download_progress should accept handler without error."""
        loader = ConcreteLoader()
        handler = MagicMock()
        loader.subscribe_download_progress(handler)

    def test_concrete_loader_unsubscribe_download_progress(self):
        """unsubscribe_download_progress should accept handler without error."""
        loader = ConcreteLoader()
        handler = MagicMock()
        loader.unsubscribe_download_progress(handler)


class IncompleteLoader(Loader):
    """Incomplete implementation missing some abstract methods."""

    def search(self, word: str) -> List[Dict[str, Any]]:
        return []

    def is_language(self, season, episode, key, language="German Dub"):
        return False

    # Deliberately omit download, get_site_key, etc.


class TestIncompleteImplementation:
    """Test that incomplete implementations cannot be instantiated."""

    def test_incomplete_loader_raises_type_error(self):
        """Loader subclass missing abstract methods cannot be instantiated."""
        with pytest.raises(TypeError):
            IncompleteLoader()


class TestLoaderMethodSignatures:
    """Test method signatures match the expected contract."""

    def test_search_returns_list(self):
        """search() should return List[Dict[str, Any]]."""
        loader = ConcreteLoader()
        result = loader.search("test")
        assert isinstance(result, list)
        for item in result:
            assert isinstance(item, dict)

    def test_download_returns_bool(self):
        """download() should return bool."""
        loader = ConcreteLoader()
        result = loader.download("/dir", "folder", 1, 1, "key")
        assert isinstance(result, bool)

    def test_get_season_episode_count_returns_dict_int_int(self):
        """get_season_episode_count() should return Dict[int, int]."""
        loader = ConcreteLoader()
        result = loader.get_season_episode_count("slug")
        assert isinstance(result, dict)
        for k, v in result.items():
            assert isinstance(k, int)
            assert isinstance(v, int)
