"""
Unit tests for Serie class to verify key validation and identifier usage.
"""

import json
import os
import tempfile

import pytest

from src.core.entities.series import Serie


class TestSerieValidation:
    """Test Serie class validation logic."""

    def test_serie_creation_with_valid_key(self):
        """Test creating Serie with valid key."""
        serie = Serie(
            key="attack-on-titan",
            name="Attack on Titan",
            site="https://aniworld.to/anime/stream/attack-on-titan",
            folder="Attack on Titan (2013)",
            episodeDict={1: [1, 2, 3], 2: [1, 2]}
        )
        
        assert serie.key == "attack-on-titan"
        assert serie.name == "Attack on Titan"
        assert serie.site == "https://aniworld.to/anime/stream/attack-on-titan"
        assert serie.folder == "Attack on Titan (2013)"
        assert serie.episodeDict == {1: [1, 2, 3], 2: [1, 2]}

    def test_serie_creation_with_empty_key_raises_error(self):
        """Test that creating Serie with empty key raises ValueError."""
        with pytest.raises(ValueError, match="key cannot be None or empty"):
            Serie(
                key="",
                name="Test Series",
                site="https://example.com",
                folder="Test Folder",
                episodeDict={1: [1]}
            )

    def test_serie_creation_with_whitespace_key_raises_error(self):
        """Test that creating Serie with whitespace-only key raises error."""
        with pytest.raises(ValueError, match="key cannot be None or empty"):
            Serie(
                key="   ",
                name="Test Series",
                site="https://example.com",
                folder="Test Folder",
                episodeDict={1: [1]}
            )

    def test_serie_key_is_stripped(self):
        """Test that Serie key is stripped of whitespace."""
        serie = Serie(
            key="  attack-on-titan  ",
            name="Attack on Titan",
            site="https://example.com",
            folder="Attack on Titan (2013)",
            episodeDict={1: [1]}
        )
        
        assert serie.key == "attack-on-titan"

    def test_serie_key_setter_with_valid_value(self):
        """Test setting key property with valid value."""
        serie = Serie(
            key="initial-key",
            name="Test",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]}
        )
        
        serie.key = "new-key"
        assert serie.key == "new-key"

    def test_serie_key_setter_with_empty_value_raises_error(self):
        """Test that setting key to empty string raises ValueError."""
        serie = Serie(
            key="initial-key",
            name="Test",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]}
        )
        
        with pytest.raises(ValueError, match="key cannot be None or empty"):
            serie.key = ""

    def test_serie_key_setter_with_whitespace_raises_error(self):
        """Test that setting key to whitespace raises ValueError."""
        serie = Serie(
            key="initial-key",
            name="Test",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]}
        )
        
        with pytest.raises(ValueError, match="key cannot be None or empty"):
            serie.key = "   "

    def test_serie_key_setter_strips_whitespace(self):
        """Test that key setter strips whitespace."""
        serie = Serie(
            key="initial-key",
            name="Test",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]}
        )
        
        serie.key = "  new-key  "
        assert serie.key == "new-key"


class TestSerieProperties:
    """Test Serie class properties and methods."""

    def test_serie_str_representation(self):
        """Test string representation of Serie."""
        serie = Serie(
            key="test-key",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1, 2]}
        )
        
        str_repr = str(serie)
        assert "key='test-key'" in str_repr
        assert "name='Test Series'" in str_repr
        assert "folder='Test Folder'" in str_repr

    def test_serie_to_dict(self):
        """Test conversion of Serie to dictionary."""
        serie = Serie(
            key="test-key",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1, 2], 2: [1, 2, 3]}
        )
        
        data = serie.to_dict()
        
        assert data["key"] == "test-key"
        assert data["name"] == "Test Series"
        assert data["site"] == "https://example.com"
        assert data["folder"] == "Test Folder"
        assert "1" in data["episodeDict"]
        assert data["episodeDict"]["1"] == [1, 2]

    def test_serie_from_dict(self):
        """Test creating Serie from dictionary."""
        data = {
            "key": "test-key",
            "name": "Test Series",
            "site": "https://example.com",
            "folder": "Test Folder",
            "episodeDict": {"1": [1, 2], "2": [1, 2, 3]}
        }
        
        serie = Serie.from_dict(data)
        
        assert serie.key == "test-key"
        assert serie.name == "Test Series"
        assert serie.folder == "Test Folder"
        assert serie.episodeDict == {1: [1, 2], 2: [1, 2, 3]}

    def test_serie_save_and_load_from_file(self):
        """Test saving and loading Serie from file."""
        import warnings
        
        serie = Serie(
            key="test-key",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1, 2, 3]}
        )
        
        # Create temporary file
        with tempfile.NamedTemporaryFile(
            mode='w',
            delete=False,
            suffix='.json'
        ) as f:
            temp_filename = f.name
        
        try:
            # Suppress deprecation warnings for this test
            with warnings.catch_warnings():
                warnings.simplefilter("ignore", DeprecationWarning)
                
                # Save to file
                serie.save_to_file(temp_filename)
                
                # Load from file
                loaded_serie = Serie.load_from_file(temp_filename)
            
            # Verify all properties match
            assert loaded_serie.key == serie.key
            assert loaded_serie.name == serie.name
            assert loaded_serie.site == serie.site
            assert loaded_serie.folder == serie.folder
            assert loaded_serie.episodeDict == serie.episodeDict
        finally:
            # Cleanup
            if os.path.exists(temp_filename):
                os.remove(temp_filename)

    def test_serie_folder_is_mutable(self):
        """Test that folder property can be changed (it's metadata only)."""
        serie = Serie(
            key="test-key",
            name="Test",
            site="https://example.com",
            folder="Old Folder",
            episodeDict={1: [1]}
        )
        
        serie.folder = "New Folder"
        assert serie.folder == "New Folder"
        # Key should remain unchanged
        assert serie.key == "test-key"


class TestSerieDocumentation:
    """Test that Serie class has proper documentation."""

    def test_serie_class_has_docstring(self):
        """Test that Serie class has a docstring."""
        assert Serie.__doc__ is not None
        assert "unique identifier" in Serie.__doc__.lower()

    def test_key_property_has_docstring(self):
        """Test that key property has descriptive docstring."""
        assert Serie.key.fget.__doc__ is not None
        assert "unique" in Serie.key.fget.__doc__.lower()
        assert "identifier" in Serie.key.fget.__doc__.lower()

    def test_folder_property_has_docstring(self):
        """Test that folder property documents it's metadata only."""
        assert Serie.folder.fget.__doc__ is not None
        assert "metadata" in Serie.folder.fget.__doc__.lower()
        assert "not used for lookups" in Serie.folder.fget.__doc__.lower()


class TestSerieDeprecationWarnings:
    """Test deprecation warnings for file-based methods."""

    def test_save_to_file_raises_deprecation_warning(self):
        """Test save_to_file() raises deprecation warning."""
        import warnings
        
        serie = Serie(
            key="test-key",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1, 2, 3]}
        )
        
        with tempfile.NamedTemporaryFile(
            mode='w', suffix='.json', delete=False
        ) as temp_file:
            temp_filename = temp_file.name
        
        try:
            with warnings.catch_warnings(record=True) as w:
                warnings.simplefilter("always")
                serie.save_to_file(temp_filename)
                
                # Check deprecation warning was raised
                assert len(w) == 1
                assert issubclass(w[0].category, DeprecationWarning)
                assert "deprecated" in str(w[0].message).lower()
                assert "save_to_file" in str(w[0].message)
        finally:
            if os.path.exists(temp_filename):
                os.remove(temp_filename)

    def test_load_from_file_raises_deprecation_warning(self):
        """Test load_from_file() raises deprecation warning."""
        import warnings
        
        serie = Serie(
            key="test-key",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1, 2, 3]}
        )
        
        with tempfile.NamedTemporaryFile(
            mode='w', suffix='.json', delete=False
        ) as temp_file:
            temp_filename = temp_file.name
        
        try:
            # Save first (suppress warning for this)
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                serie.save_to_file(temp_filename)
            
            # Now test loading
            with warnings.catch_warnings(record=True) as w:
                warnings.simplefilter("always")
                Serie.load_from_file(temp_filename)
                
                # Check deprecation warning was raised
                assert len(w) == 1
                assert issubclass(w[0].category, DeprecationWarning)
                assert "deprecated" in str(w[0].message).lower()
                assert "load_from_file" in str(w[0].message)
        finally:
            if os.path.exists(temp_filename):
                os.remove(temp_filename)


class TestSerieSanitizedFolder:
    """Test Serie.sanitized_folder property."""

    def test_sanitized_folder_from_name(self):
        """Test that sanitized_folder uses the name property."""
        serie = Serie(
            key="attack-on-titan",
            name="Attack on Titan: Final Season",
            site="aniworld.to",
            folder="old-folder",
            episodeDict={}
        )
        
        result = serie.sanitized_folder
        assert ":" not in result
        assert "Attack on Titan" in result

    def test_sanitized_folder_removes_special_chars(self):
        """Test that special characters are removed."""
        serie = Serie(
            key="re-zero",
            name="Re:Zero - Starting Life in Another World?",
            site="aniworld.to",
            folder="old-folder",
            episodeDict={}
        )
        
        result = serie.sanitized_folder
        assert ":" not in result
        assert "?" not in result

    def test_sanitized_folder_fallback_to_folder(self):
        """Test fallback to folder when name is empty."""
        serie = Serie(
            key="test-key",
            name="",
            site="aniworld.to",
            folder="Valid Folder Name",
            episodeDict={}
        )
        
        result = serie.sanitized_folder
        assert result == "Valid Folder Name"

    def test_sanitized_folder_fallback_to_key(self):
        """Test fallback to key when name and folder can't be sanitized."""
        serie = Serie(
            key="valid-key",
            name="",
            site="aniworld.to",
            folder="",
            episodeDict={}
        )
        
        result = serie.sanitized_folder
        assert result == "valid-key"

    def test_sanitized_folder_preserves_unicode(self):
        """Test that Unicode characters are preserved."""
        serie = Serie(
            key="japanese-anime",
            name="進撃の巨人",
            site="aniworld.to",
            folder="old-folder",
            episodeDict={}
        )
        
        result = serie.sanitized_folder
        assert "進撃の巨人" in result

    def test_sanitized_folder_with_various_anime_titles(self):
        """Test sanitized_folder with real anime titles."""
        test_cases = [
            ("fate-stay-night", "Fate/Stay Night: UBW"),
            ("86-eighty-six", "86: Eighty-Six"),
            ("steins-gate", "Steins;Gate"),
        ]
        
        for key, name in test_cases:
            serie = Serie(
                key=key,
                name=name,
                site="aniworld.to",
                folder="old-folder",
                episodeDict={}
            )
            result = serie.sanitized_folder
            # Verify invalid filesystem characters are removed
            # Note: semicolon is valid on Linux but we test common invalid chars
            assert ":" not in result
            assert "/" not in result


class TestSerieNFOFeatures:
    """Test Serie class NFO-related features."""

    def test_serie_creation_with_nfo_path(self):
        """Test creating Serie with NFO path."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]},
            nfo_path="/path/to/tvshow.nfo"
        )
        
        assert serie.nfo_path == "/path/to/tvshow.nfo"

    def test_serie_creation_without_nfo_path(self):
        """Test creating Serie without NFO path defaults to None."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]}
        )
        
        assert serie.nfo_path is None

    def test_serie_nfo_path_setter(self):
        """Test setting NFO path property."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]}
        )
        
        serie.nfo_path = "/new/path/tvshow.nfo"
        assert serie.nfo_path == "/new/path/tvshow.nfo"

    def test_has_nfo_with_existing_file(self, tmp_path):
        """Test has_nfo returns True when NFO file exists."""
        # Create a test directory structure
        base_dir = tmp_path / "anime"
        series_dir = base_dir / "Test Series"
        series_dir.mkdir(parents=True)
        
        nfo_file = series_dir / "tvshow.nfo"
        nfo_file.write_text("test nfo content")
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_nfo(str(base_dir)) is True

    def test_has_nfo_with_missing_file(self, tmp_path):
        """Test has_nfo returns False when NFO file doesn't exist."""
        base_dir = tmp_path / "anime"
        base_dir.mkdir(parents=True)
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_nfo(str(base_dir)) is False

    def test_has_nfo_with_nfo_path_set(self, tmp_path):
        """Test has_nfo using nfo_path when base_directory not provided."""
        nfo_file = tmp_path / "tvshow.nfo"
        nfo_file.write_text("test nfo content")
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]},
            nfo_path=str(nfo_file)
        )
        
        assert serie.has_nfo() is True

    def test_has_nfo_without_base_directory_or_path(self):
        """Test has_nfo returns False when no base_directory or nfo_path."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_nfo() is False

    def test_has_poster_with_existing_file(self, tmp_path):
        """Test has_poster returns True when poster.jpg exists."""
        base_dir = tmp_path / "anime"
        series_dir = base_dir / "Test Series"
        series_dir.mkdir(parents=True)
        
        poster_file = series_dir / "poster.jpg"
        poster_file.write_text("test image data")
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_poster(str(base_dir)) is True

    def test_has_poster_with_missing_file(self, tmp_path):
        """Test has_poster returns False when poster.jpg doesn't exist."""
        base_dir = tmp_path / "anime"
        base_dir.mkdir(parents=True)
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_poster(str(base_dir)) is False

    def test_has_poster_without_base_directory(self):
        """Test has_poster returns False when no base_directory provided."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_poster() is False

    def test_has_logo_with_existing_file(self, tmp_path):
        """Test has_logo returns True when logo.png exists."""
        base_dir = tmp_path / "anime"
        series_dir = base_dir / "Test Series"
        series_dir.mkdir(parents=True)
        
        logo_file = series_dir / "logo.png"
        logo_file.write_text("test logo data")
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_logo(str(base_dir)) is True

    def test_has_logo_with_missing_file(self, tmp_path):
        """Test has_logo returns False when logo.png doesn't exist."""
        base_dir = tmp_path / "anime"
        base_dir.mkdir(parents=True)
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_logo(str(base_dir)) is False

    def test_has_logo_without_base_directory(self):
        """Test has_logo returns False when no base_directory provided."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_logo() is False

    def test_has_fanart_with_existing_file(self, tmp_path):
        """Test has_fanart returns True when fanart.jpg exists."""
        base_dir = tmp_path / "anime"
        series_dir = base_dir / "Test Series"
        series_dir.mkdir(parents=True)
        
        fanart_file = series_dir / "fanart.jpg"
        fanart_file.write_text("test fanart data")
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_fanart(str(base_dir)) is True

    def test_has_fanart_with_missing_file(self, tmp_path):
        """Test has_fanart returns False when fanart.jpg doesn't exist."""
        base_dir = tmp_path / "anime"
        base_dir.mkdir(parents=True)
        
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_fanart(str(base_dir)) is False

    def test_has_fanart_without_base_directory(self):
        """Test has_fanart returns False when no base_directory provided."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1]}
        )
        
        assert serie.has_fanart() is False

    def test_to_dict_includes_nfo_path(self):
        """Test that to_dict includes nfo_path field."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1, 2], 2: [1]},
            year=2024,
            nfo_path="/path/to/tvshow.nfo"
        )
        
        result = serie.to_dict()
        
        assert result["nfo_path"] == "/path/to/tvshow.nfo"
        assert result["key"] == "test-series"
        assert result["name"] == "Test Series"
        assert result["year"] == 2024

    def test_to_dict_with_none_nfo_path(self):
        """Test that to_dict handles None nfo_path."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1]}
        )
        
        result = serie.to_dict()
        
        assert result["nfo_path"] is None

    def test_from_dict_with_nfo_path(self):
        """Test that from_dict correctly loads nfo_path."""
        data = {
            "key": "test-series",
            "name": "Test Series",
            "site": "https://example.com",
            "folder": "Test Folder",
            "episodeDict": {"1": [1, 2]},
            "year": 2024,
            "nfo_path": "/path/to/tvshow.nfo"
        }
        
        serie = Serie.from_dict(data)
        
        assert serie.nfo_path == "/path/to/tvshow.nfo"
        assert serie.key == "test-series"
        assert serie.year == 2024

    def test_from_dict_without_nfo_path(self):
        """Test that from_dict handles missing nfo_path (backward compatibility)."""
        data = {
            "key": "test-series",
            "name": "Test Series",
            "site": "https://example.com",
            "folder": "Test Folder",
            "episodeDict": {"1": [1, 2]}
        }
        
        serie = Serie.from_dict(data)
        
        assert serie.nfo_path is None
        assert serie.key == "test-series"

    def test_save_and_load_file_with_nfo_path(self, tmp_path):
        """Test that save_to_file and load_from_file preserve nfo_path."""
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Folder",
            episodeDict={1: [1, 2], 2: [1]},
            year=2024,
            nfo_path="/path/to/tvshow.nfo"
        )
        
        file_path = tmp_path / "data"
        
        with pytest.warns(DeprecationWarning):
            serie.save_to_file(str(file_path))
        
        with pytest.warns(DeprecationWarning):
            loaded_serie = Serie.load_from_file(str(file_path))
        
        assert loaded_serie.nfo_path == "/path/to/tvshow.nfo"
        assert loaded_serie.key == "test-series"
        assert loaded_serie.year == 2024

