import json
import logging
import os
import warnings
from pathlib import Path
from typing import Optional

from src.server.utils.filesystem import sanitize_folder_name

logger = logging.getLogger(__name__)


class Serie:
    """
    Represents an anime series with metadata and episode information.
    
    The `key` property is the unique identifier for the series
    (provider-assigned, URL-safe).
    The `folder` property is the filesystem folder name
    (metadata only, not used for lookups).
    
    Args:
        key: Unique series identifier from provider
            (e.g., "attack-on-titan"). Cannot be empty.
        name: Display name of the series
        site: Provider site URL
        folder: Filesystem folder name (metadata only,
            e.g., "Attack on Titan (2013)")
        episodeDict: Dictionary mapping season numbers to
            lists of episode numbers
        year: Release year of the series (optional)
        
    Raises:
        ValueError: If key is None or empty string
    """
    
    def __init__(
        self,
        key: str,
        name: str,
        site: str,
        folder: str,
        episodeDict: dict[int, list[int]],
        year: int | None = None,
        nfo_path: Optional[str] = None
    ):
        if not key or not key.strip():
            raise ValueError("Serie key cannot be None or empty")
        
        self._key = key.strip()
        self._name = name
        self._site = site
        self._folder = folder
        self._episodeDict = episodeDict
        self._year = year
        self._nfo_path = nfo_path
        
    def __str__(self):
        """String representation of Serie object"""
        year_str = f", year={self.year}" if self.year else ""
        return (
            f"Serie(key='{self.key}', name='{self.name}', "
            f"site='{self.site}', folder='{self.folder}', "
            f"episodeDict={self.episodeDict}{year_str})"
        )

    def __repr__(self):
        """Concise developer representation of Serie object."""
        season_count = len(self.episodeDict)
        episode_count = sum(len(eps) for eps in self.episodeDict.values())
        year_str = f", year={self.year}" if self.year else ""
        return (
            f"Serie(key={self.key!r}, name={self.name!r}"
            f"{year_str}, seasons={season_count}, episodes={episode_count})"
        )

    @property
    def key(self) -> str:
        """
        Unique series identifier (primary identifier for all lookups).
        
        This is the provider-assigned, URL-safe identifier used
        throughout the application for series identification,
        lookups, and operations.
        
        Returns:
            str: The unique series key
        """
        return self._key

    @key.setter
    def key(self, value: str):
        """
        Set the unique series identifier.
        
        Args:
            value: New key value
            
        Raises:
            ValueError: If value is None or empty string
        """
        if not value or not value.strip():
            raise ValueError("Serie key cannot be None or empty")
        self._key = value.strip()

    @property
    def name(self) -> str:
        return self._name

    @name.setter
    def name(self, value: str):
        self._name = value

    @property
    def site(self) -> str:
        return self._site

    @site.setter
    def site(self, value: str):
        self._site = value

    @property
    def folder(self) -> str:
        """
        Filesystem folder name (metadata only, not used for lookups).
        
        This property contains the local directory name where the series
        files are stored. It should NOT be used as an identifier for
        series lookups - use `key` instead.
        
        Returns:
            str: The filesystem folder name
        """
        return self._folder

    @folder.setter
    def folder(self, value: str):
        """
        Set the filesystem folder name.
        
        Args:
            value: Folder name for the series
        """
        self._folder = value

    @property
    def episodeDict(self) -> dict[int, list[int]]:
        return self._episodeDict

    @episodeDict.setter
    def episodeDict(self, value: dict[int, list[int]]):
        self._episodeDict = value

    @property
    def year(self) -> int | None:
        """
        Release year of the series.
        
        Returns:
            int or None: The year the series was released, or None if unknown
        """
        return self._year

    @year.setter
    def year(self, value: int | None):
        """Set the release year of the series."""
        self._year = value

    @property
    def nfo_path(self) -> Optional[str]:
        """
        Path to the tvshow.nfo metadata file.
        
        Returns:
            str or None: Path to the NFO file, or None if not set
        """
        return self._nfo_path

    @nfo_path.setter
    def nfo_path(self, value: Optional[str]):
        """Set the path to the NFO file."""
        self._nfo_path = value

    def has_nfo(self, base_directory: Optional[str] = None) -> bool:
        """
        Check if tvshow.nfo file exists for this series.
        
        Args:
            base_directory: Base anime directory path. If provided, checks
                relative to base_directory/folder/tvshow.nfo. If not provided,
                uses nfo_path directly.
        
        Returns:
            bool: True if tvshow.nfo exists, False otherwise
        """
        if base_directory:
            nfo_file = Path(base_directory) / self.folder / "tvshow.nfo"
        elif self._nfo_path:
            nfo_file = Path(self._nfo_path)
        else:
            return False
        
        return nfo_file.exists() and nfo_file.is_file()

    def has_poster(self, base_directory: Optional[str] = None) -> bool:
        """
        Check if poster.jpg file exists for this series.
        
        Args:
            base_directory: Base anime directory path. If provided, checks
                relative to base_directory/folder/poster.jpg.
        
        Returns:
            bool: True if poster.jpg exists, False otherwise
        """
        if not base_directory:
            return False
        
        poster_file = Path(base_directory) / self.folder / "poster.jpg"
        return poster_file.exists() and poster_file.is_file()

    def has_logo(self, base_directory: Optional[str] = None) -> bool:
        """
        Check if logo.png file exists for this series.
        
        Args:
            base_directory: Base anime directory path. If provided, checks
                relative to base_directory/folder/logo.png.
        
        Returns:
            bool: True if logo.png exists, False otherwise
        """
        if not base_directory:
            return False
        
        logo_file = Path(base_directory) / self.folder / "logo.png"
        return logo_file.exists() and logo_file.is_file()

    def has_fanart(self, base_directory: Optional[str] = None) -> bool:
        """
        Check if fanart.jpg file exists for this series.
        
        Args:
            base_directory: Base anime directory path. If provided, checks
                relative to base_directory/folder/fanart.jpg.
        
        Returns:
            bool: True if fanart.jpg exists, False otherwise
        """
        if not base_directory:
            return False
        
        fanart_file = Path(base_directory) / self.folder / "fanart.jpg"
        return fanart_file.exists() and fanart_file.is_file()

    @property
    def name_with_year(self) -> str:
        """
        Get the series name with year appended if available.
        
        Returns a name in the format "Name (Year)" if year is available,
        otherwise returns just the name. This should be used for creating
        filesystem folders to distinguish series with the same name.
        
        Returns:
            str: Name with year in format "Name (Year)", or just name if no year
            
        Example:
            >>> serie = Serie("dororo", "Dororo", ..., year=2025)
            >>> serie.name_with_year
            'Dororo (2025)'
        """
        if self._year:
            import re
            year_suffix = f" ({self._year})"
            # Strip ALL trailing year suffixes before appending to prevent duplication
            clean_name = re.sub(r'(\s*\(\d{4}\))+\s*$', '', self._name).strip()
            return f"{clean_name}{year_suffix}"
        return self._name

    @property
    def sanitized_folder(self) -> str:
        """
        Get a filesystem-safe folder name derived from the display name with year.
        
        This property returns a sanitized version of the series name with year
        (if available) suitable for use as a filesystem folder name. It removes/
        replaces characters that are invalid for filesystems while preserving
        Unicode characters.
        
        Use this property when creating folders for the series on disk.
        The `folder` property stores the actual folder name used.
        
        Returns:
            str: Filesystem-safe folder name based on display name with year
            
        Example:
            >>> serie = Serie("attack-on-titan", "Attack on Titan: Final", ..., year=2025)
            >>> serie.sanitized_folder
            'Attack on Titan Final (2025)'
        """
        # Use name_with_year if available, fall back to folder, then key
        name_to_sanitize = self.name_with_year or self._folder or self._key
        try:
            return sanitize_folder_name(name_to_sanitize)
        except ValueError:
            # Fallback to key if name cannot be sanitized
            return sanitize_folder_name(self._key)

    def ensure_folder_with_year(self) -> str:
        """Ensure folder name includes year if available.
        
        If the serie has a year and the current folder name doesn't include it,
        updates the folder name to include the year in format "Name (Year)".
        
        This method should be called before creating folders or NFO files to
        ensure consistent naming across the application.
        
        Returns:
            str: The folder name (updated if needed)
            
        Example:
            >>> serie = Serie("perfect-blue", "Perfect Blue", ..., folder="Perfect Blue", year=1997)
            >>> serie.ensure_folder_with_year()
            'Perfect Blue (1997)'
            >>> serie.folder  # folder property is updated
            'Perfect Blue (1997)'
        """
        if self._year:
            # Check if folder already has year format
            year_pattern = f"({self._year})"
            if year_pattern not in self._folder:
                # Update folder to include year
                self._folder = self.sanitized_folder
                logger.info(
                    f"Updated folder name for '{self._key}' to include year: {self._folder}"
                )
        return self._folder

    def to_dict(self):
        """Convert Serie object to dictionary for JSON serialization."""
        return {
            "key": self.key,
            "name": self.name,
            "site": self.site,
            "folder": self.folder,
            "episodeDict": {
                str(k): list(v) for k, v in self.episodeDict.items()
            },
            "year": self.year,
            "nfo_path": self.nfo_path
        }

    @staticmethod
    def from_dict(data: dict):
        """Create a Serie object from dictionary."""
        # Convert keys to int
        episode_dict = {
            int(k): v for k, v in data["episodeDict"].items()
        }
        return Serie(
            data["key"],
            data["name"],
            data["site"],
            data["folder"],
            episode_dict,
            data.get("year"),  # Optional year field for backward compatibility
            data.get("nfo_path")  # Optional nfo_path field
        )

    def save_to_file(self, filename: str):
        """Save Serie object to JSON file.
        
        .. deprecated::
            File-based storage is deprecated. Use database storage via
            `AnimeSeriesService.create()` instead. This method will be
            removed in v3.0.0.
            
        Args:
            filename: Path to save the JSON file
        """
        warnings.warn(
            "save_to_file() is deprecated and will be removed in v3.0.0. "
            "Use database storage via AnimeSeriesService.create() instead.",
            DeprecationWarning,
            stacklevel=2
        )
        with open(filename, "w", encoding="utf-8") as file:
            json.dump(self.to_dict(), file, indent=4)

    @classmethod
    def load_from_file(cls, filename: str) -> "Serie":
        """Load Serie object from JSON file.
        
        .. deprecated::
            File-based storage is deprecated. Use database storage via
            `AnimeSeriesService.get_by_key()` instead. This method will be
            removed in v3.0.0.
            
        Args:
            filename: Path to load the JSON file from
            
        Returns:
            Serie: The loaded Serie object
        """
        warnings.warn(
            "load_from_file() is deprecated and will be removed in v3.0.0. "
            "Use database storage via AnimeSeriesService instead.",
            DeprecationWarning,
            stacklevel=2
        )
        with open(filename, "r", encoding="utf-8") as file:
            data = json.load(file)
        return cls.from_dict(data)
