from mongoengine.queryset import Q

from app.models.genre import Genre
from app.services.mapper_service import IDMapper, ExistChecker


class GenreService:
    """
    Service class for creating and retrieving genres.

    Methods:
        create_genre(data: dict) -> dict:
            Creates a new genre and returns its data as a dictionary.

        get_all_genres() -> list[dict]:
            Retrieves all genres from the database and returns their data as a list of dictionaries.

        get_genre_by_id(genre_id: str) -> dict:
            Retrieves a single genre by its ID and returns its data as a dictionary.

        update_genre(genre_id: str, data: dict) -> dict:
            Updates an existing genre and returns its updated data as a dictionary.

        delete_genre(genre_id: str) -> bool:
            Deletes a genre by its ID and returns True if successful, False otherwise.

        search_genres(query: str) -> list[dict]:
            Searches for genres based on a query string in 'name,' 'description,' or 'category' fields.

        search_genres_v2(query: str) -> list[dict]:
            Searches for genres based on a query string in all fields of the 'Genre' model.

        update_genre_field(genre_id: str, field_name: str, new_value) -> dict:
            Update a specific field of an existing genre by its ID.

        get_genres_by_category(category: str) -> list:
            Retrieve all genres within a specific category.

        get_genres_by_name(name: str) -> list:
            Retrieve genres with a specific name.

        count_genres() -> int:
            Count the total number of genres in the database.

        delete_genres_by_category(category: str) -> int:
            Delete all genres within a specific category.

        delete_all_genres() -> int:
            Delete all genres in the database.

        genre_exists(name: str) -> bool:
            Check if a genre with a specific ID exists in the database.

        update_partial_genre(genre_id: str, data: dict) -> dict:
            Partially update an existing genre by its ID.
    """

    @staticmethod
    def create_genre(data: dict) -> dict:
        """
        Create a new genre and save it to the database.

        Args:
            data (dict): A dictionary containing genre data.

        Returns:
            dict: The created genre data as a dictionary.

        Example:
            # Example data for creating a genre
            genre_data = {
                'name': 'Action',
                'description': 'Action Genre Description',
                'category': 'Program'  # Specify the category (Channel or Program) as needed.
            }

            # Create a new genre using the GenreService
            new_genre_data = GenreService.create_genre(genre_data)

        """
        # Create a new Genre object with the provided data
        genre = Genre(**data)
        # Save the newly created genre to the database
        genre.save()
        # Convert the genre object to a dictionary representation
        return genre
        
    @staticmethod
    def get_all_genres() -> list:
        """
        Retrieve all genres from the database.

        Returns:
            List: A list of all genre data as dictionaries.

        Example:
            all_genres_data = GenreService.get_all_genres()
        """
        # Query the database to retrieve all genre objects
        genres = Genre.objects() 
        # Convert each genre object to a dictionary representation and create a list of them
        genre_data_list = [genre.to_dict() for genre in genres] 
        # Return the list of genre data as dictionaries
        return genre_data_list
    
    @staticmethod
    def get_genre_by_id(genre_id: str) -> dict:
        """
        Retrieve a single genre by its ID.

        Args:
            genre_id (str): The ID of the genre to retrieve.

        Returns:
            dict: The genre data as a dictionary.

        Example:
            genre_data = GenreService.get_genre_by_id('12345')
        """

        # Map the public ID to the internal ID for the Genre model
        genre_id = IDMapper.public_id_mapper(genre_id, Genre)
        # Check if the mapping was successful and if an internal ID exists
        if not genre_id:
            return None
        # Query the database to retrieve the genre by its internal ID
        genre = Genre.objects(id=genre_id).first()
        # Convert the genre object to a dictionary representation
        return genre
    
    @staticmethod
    def update_genre(genre_id: str, data: dict) -> dict:
        """
        Update an existing genre by its ID.

        Args:
            genre_id (str): The ID of the genre to update.
            data (dict): A dictionary containing the updated genre data.

        Returns:
            dict: The updated genre data as a dictionary.

        Example:
            updated_genre_data = GenreService.update_genre('12345', {'name': 'Updated Genre Name', 'description': 'Updated Description'})
        """
        # Retrieve the existing genre data by its ID using the get_genre_by_id function
        genre = GenreService.get_genre_by_id(genre_id)

        # Check if the genre exists
        if genre:
            # Update the genre fields with the provided data
            genre.modify(**data) 
            # Save the updated genre data
            genre.update() 
            # Convert the updated genre object to a dictionary representation
            return genre
        else:
            # Return None or raise an exception if the genre with the specified ID doesn't exist.
            return None
        
    @staticmethod
    def delete_genre(genre_id: str) -> bool:
        """
        Delete a genre by its ID.

        Args:
            genre_id (str): The ID of the genre to delete.

        Returns:
            bool: True if the genre was successfully deleted, False otherwise.

        Example:
            result = GenreService.delete_genre('12345')
        """
        # Retrieve the existing genre data by its ID using the get_genre_by_id function
        genre = GenreService.get_genre_by_id(genre_id)

        # Check if the genre exists
        if genre:
            # Delete the genre from the database
            genre.delete()
            # Return True to indicate a successful deletion
            return True
        else:
            # Return False if the genre with the specified ID doesn't exist.
            return False
        
    @staticmethod
    def search_genres(query: str) -> list:
        """
        Search for genres based on a query string in 'name,' 'description,' or 'category' fields.

        Args:
            query (str): The search query.

        Returns:
            List: A list of matching genre data as dictionaries.

        Example:
            matching_genres = GenreService.search_genres('action')
        """
        # Create a query using the `$or` operator to search in 'name,' 'description,' and 'category' fields
        genres = Genre.objects(
            Q(name__icontains=query) | Q(description__icontains=query) | Q(category__icontains=query)
        )

        # Convert the matching genre objects to a list of dictionaries
        return [genre.to_dict() for genre in genres]
    
    @staticmethod
    def search_genres_v2(query: str) -> list:
        """
        Search for genres based on a query string in all fields of the 'Genre' model.

        Args:
            query (str): The search query.

        Returns:
            List: A list of matching genre data as dictionaries.

        Example:
            matching_genres = GenreService.search_genres('action')
        """
        # Create a query using the `$text` operator to search in all fields
        genres = Genre.objects(
            Q(__raw__={'$text': {'$search': query}})
        )

        # Convert the matching genre objects to a list of dictionaries
        return [genre.to_dict() for genre in genres]
    
    @staticmethod
    def update_genre_field(genre_id: str, field_name: str, new_value) -> dict:
        """
        Update a specific field of an existing genre by its ID.

        Args:
            genre_id (str): The ID of the genre to update.
            field_name (str): The name of the field to update.
            new_value: The new value for the field.

        Returns:
            dict: The updated genre data as a dictionary.

        Example:
            updated_genre_data = GenreService.update_genre_field('12345', 'name', 'Updated Genre Name')
        """
        # Retrieve the existing genre data by its ID using the get_genre_by_id function
        genre = GenreService.get_genre_by_id(genre_id)

        # Check if the genre exists
        if genre:
            # Update the specific field with the new value
            setattr(genre, field_name, new_value)
            genre.save()  # Save the changes to the database
            # Convert the updated genre object to a dictionary representation
            return genre.to_dict()
        else:
            # Return None or raise an exception if the genre with the specified ID doesn't exist.
            return None

    @staticmethod
    def get_genres_by_category(category: str) -> list:
        """
        Retrieve all genres within a specific category.

        Args:
            category (str): The category to filter genres by.

        Returns:
            List: A list of genre data as dictionaries within the specified category.

        Example:
            action_genres = GenreService.get_genres_by_category('Action')
        """
        # Query the database to retrieve genres in the specified category
        genres = Genre.objects(category=category)
        # Convert the matching genre objects to a list of dictionaries
        return [genre.to_dict() for genre in genres]
    
    @staticmethod
    def get_genres_by_name(name: str) -> list:
        """
        Retrieve genres with a specific name.

        Args:
            name (str): The name of the genre to search for.

        Returns:
            List: A list of genre data as dictionaries with the specified name.

        Example:
            matching_genres = GenreService.get_genres_by_name('Action')
        """
        # Query the database to retrieve genres with the specified name
        genres = Genre.objects(name=name)
        # Convert the matching genre objects to a list of dictionaries
        return [genre.to_dict() for genre in genres]

    @staticmethod
    def count_genres() -> int:
        """
        Count the total number of genres in the database.

        Returns:
            int: The total count of genres.

        Example:
            total_genre_count = GenreService.count_genres()
        """
        # Use the .count() method to count the number of genres in the database
        return Genre.objects().count()
    
    @staticmethod
    def delete_genres_by_category(category: str) -> int:
        """
        Delete all genres within a specific category.

        Args:
            category (str): The category to filter genres by.

        Returns:
            int: The number of genres deleted.

        Example:
            deleted_count = GenreService.delete_genres_by_category('Action')
        """
        # Query the database to retrieve genres in the specified category
        genres = Genre.objects(category=category)
        # Initialize a count for deleted genres
        deleted_count = 0
        # Iterate through the list of genres in the specified category and delete each one
        for genre in genres:
            # Delete the genre from the database
            genre.delete()
            deleted_count += 1
        # Return the total count of deleted genres
        return deleted_count
    
    @staticmethod
    def delete_all_genres() -> int:
        """
        Delete all genres in the database.

        Returns:
            int: The number of genres deleted.

        Example:
            deleted_count = GenreService.delete_all_genres()
        """
        # Query the database to retrieve all genres
        genres = Genre.objects()
        # Initialize a count for deleted genres
        deleted_count = 0
        # Iterate through the list of genres and delete each one
        for genre in genres:
            # Delete the genre from the database
            genre.delete()
            deleted_count += 1
        # Return the total count of deleted genres
        return deleted_count

    @staticmethod
    def genre_exists(name: str) -> bool:
        """
        Check if a genre with a specific ID exists in the database.

        Args:
            genre_id (str): The ID of the genre to check.

        Returns:
            bool: True if the genre exists, False otherwise.

        Example:
            exists = GenreService.genre_exists('news')
        """
        # Query the database to check if a genre with the specified ID exists
        return ExistChecker.exist(name, Genre) 

    @staticmethod
    def update_partial_genre(genre_id: str, data: dict) -> dict:
        """
        Partially update an existing genre by its ID.

        Args:
            genre_id (str): The ID of the genre to update.
            data (dict): A dictionary containing the fields to update.

        Returns:
            dict: The updated genre data as a dictionary.

        Example:
            updated_genre_data = GenreService.update_partial_genre('12345', {'description': 'New Description'})
        """
        # Retrieve the existing genre data by its ID using the get_genre_by_id function
        genre = GenreService.get_genre_by_id(genre_id)
        # Check if the genre exists
        if genre:
            # Update the specified fields with the provided data
            for field, value in data.items():
                setattr(genre, field, value)
            # Save the changes to the database
            genre.update()  
            # Convert the updated genre object to a dictionary representation
            return genre.to_dict()
        else:
            # Return None or raise an exception if the genre with the specified ID doesn't exist.
            return None

    @staticmethod
    def archive_genre(genre_id: str) -> bool:
        """
        Soft-Delete a genre by its ID.

        Args:
            genre_id (str): The ID of the genre to delete.

        Returns:
            bool: True if the genre was successfully deleted, False otherwise.

        Example:
            result = GenreService.delete_genre('12345')
        """
        # Retrieve the existing genre data by its ID using the get_genre_by_id function
        genre = GenreService.get_genre_by_id(genre_id)

        # Check if the genre exists
        if genre:
            # Soft-Delete the genre from the database
            genre.archive()
            # Return True to indicate a successful deletion
            return True
        else:
            # Return False if the genre with the specified ID doesn't exist.
            return False