from mongoengine.queryset import Q

from app.models.channel import Channel
from app.services.mapper_service import IDMapper, ExistChecker

class ChannelService:
    """
    Service class for creating and retrieving TV channels.

    Methods:
        create_channel(data: dict) -> dict:
            Creates a new TV channel and returns its data as a dictionary.

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

        get_channel_by_id(channel_id: str) -> dict:
            Retrieves a single TV channel by its ID and returns its data as a dictionary.

        update_channel(channel_id: str, data: dict) -> dict:
            Updates an existing TV channel and returns its updated data as a dictionary.

        delete_channel(channel_id: str) -> bool:
            Deletes a TV channel by its ID and returns True if successful, False otherwise.

        toggle_active_status(channel_id: str) -> dict:
            Toggles the active status of a TV channel and returns its updated data as a dictionary.

        search_channels(query: str) -> list[dict]:
            Searches for TV channels based on a query string in 'name,' 'description,' 'language,' or 'country' fields.

        count_channels() -> int:
            Count the total number of TV channels in the database.

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

        Args:
            data (dict): A dictionary containing TV channel data.

        Returns:
            dict: The created TV channel data as a dictionary.

        Example:
            # Example data for creating a TV channel
            channel_data = {
                'name': 'My Channel',
                'description': 'My Channel Description',
                'logo': 'https://example.com/logo.png',
                'language': 'English',
                'country': 'United States',
                'stream_url': 'https://example.com/stream',
                'is_active': True
            }

            # Create a new TV channel using the ChannelService
            new_channel_data = ChannelService.create_channel(channel_data)

        """
        # Create a new Channel object with the provided data
        channel = Channel(**data)
        # Save the newly created TV channel to the database
        channel.save()
        # Convert the channel object to a dictionary representation
        return channel.to_dict()

    @staticmethod
    def get_all_channels() -> list:
        """
        Retrieve all TV channels from the database.

        Returns:
            List: A list of all TV channel data as dictionaries.

        Example:
            all_channels_data = ChannelService.get_all_channels()
        """
        # Query the database to retrieve all TV channel objects
        channels = Channel.objects()
        # Convert each TV channel object to a dictionary representation and create a list of them
        channel_data_list = [channel.to_dict() for channel in channels]
        # Return the list of TV channel data as dictionaries
        return channel_data_list

    @staticmethod
    def get_channel_by_id(channel_id: str) -> dict:
        """
        Retrieve a single TV channel by its ID.

        Args:
            channel_id (str): The ID of the TV channel to retrieve.

        Returns:
            dict: The TV channel data as a dictionary.

        Example:
            channel_data = ChannelService.get_channel_by_id('12345')
        """

        # Map the public ID to the internal ID for the Channel model
        channel_id = IDMapper.public_id_mapper(channel_id, Channel)
        # Check if the mapping was successful and if an internal ID exists
        if not channel_id:
            return None
        # Query the database to retrieve the TV channel by its internal ID
        channel = Channel.objects(id=channel_id).first()
        # Convert the TV channel object to a dictionary representation
        return channel

    @staticmethod
    def update_channel(channel_id: str, data: dict) -> dict:
        """
        Update an existing TV channel by its ID.

        Args:
            channel_id (str): The ID of the TV channel to update.
            data (dict): A dictionary containing the updated TV channel data.

        Returns:
            dict: The updated TV channel data as a dictionary.

        Example:
            updated_channel_data = ChannelService.update_channel('12345', {'name': 'Updated Channel Name', 'description': 'Updated Description'})
        """
        # Retrieve the existing TV channel data by its ID using the get_channel_by_id function
        channel = ChannelService.get_channel_by_id(channel_id)

        # Check if the TV channel exists
        if channel:
            # Update the TV channel fields with the provided data
            channel.modify(**data)
            # Save the updated TV channel data
            channel.update()
            # Convert the updated TV channel object to a dictionary representation
            return channel
        else:
            # Return None or raise an exception if the TV channel with the specified ID doesn't exist.
            return None

    @staticmethod
    def delete_channel(channel_id: str) -> bool:
        """
        Delete a TV channel by its ID.

        Args:
            channel_id (str): The ID of the TV channel to delete.

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

        Example:
            result = ChannelService.delete_channel('12345')
        """
        # Retrieve the existing TV channel data by its ID using the get_channel_by_id function
        channel = ChannelService.get_channel_by_id(channel_id)

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

    @staticmethod
    def toggle_active_status(channel_id: str) -> dict:
        """
        Toggle the active status of a TV channel.

        Args:
            channel_id (str): The ID of the TV channel to toggle.

        Returns:
            dict: The updated TV channel data as a dictionary.

        Example:
            updated_channel_data = ChannelService.toggle_active_status('12345')
        """
        # Retrieve the existing TV channel data by its ID using the get_channel_by_id function
        channel = ChannelService.get_channel_by_id(channel_id)

        # Check if the TV channel exists
        if channel:
            # Toggle the 'is_active' field
            channel.is_active = not channel.is_active
            # Save the updated TV channel data
            channel.save()
            # Convert the updated TV channel object to a dictionary representation
            return channel.to_dict()
        else:
            # Return None or raise an exception if the TV channel with the specified ID doesn't exist.
            return None

    @staticmethod
    def count_channels() -> int:
        """
        Count the total number of TV channels in the database.

        Returns:
            int: The total count of TV channels.

        Example:
            total_channel_count = ChannelService.count_channels()
        """
        # Use the .count() method to count the number of TV channels in the database
        return Channel.objects().count()

    @staticmethod
    def channel_exists(name: str) -> bool:
        """
        Check if a TV channel with a specific name exists in the database.

        Args:
            name (str): The name of the TV channel to check.

        Returns:
            bool: True if the TV channel exists, False otherwise.

        Example:
            exists = ExistChecker.exist(name, Channel)
        """
        # Query the database to check if a TV channel with the specified name exists
        return ExistChecker.exist(name, Channel)

    @staticmethod
    def get_active_channels() -> list:
        """
        Retrieve all active TV channels from the database.

        Returns:
            List: A list of active TV channel data as dictionaries.

        Example:
            active_channels = ChannelService.get_active_channels()
        """
        # Query the database to retrieve active TV channel objects
        channels = Channel.objects(is_active=True)
        # Convert each active TV channel object to a dictionary representation and create a list of them
        channel_data_list = [channel.to_dict() for channel in channels]
        # Return the list of active TV channel data as dictionaries
        return channel_data_list

    @staticmethod
    def get_inactive_channels() -> list:
        """
        Retrieve all inactive TV channels from the database.

        Returns:
            List: A list of inactive TV channel data as dictionaries.

        Example:
            inactive_channels = ChannelService.get_inactive_channels()
        """
        # Query the database to retrieve inactive TV channel objects
        channels = Channel.objects(is_active=False)
        # Convert each inactive TV channel object to a dictionary representation and create a list of them
        channel_data_list = [channel.to_dict() for channel in channels]
        # Return the list of inactive TV channel data as dictionaries
        return channel_data_list

    @staticmethod
    def update_partial_channel(channel_id: str, data: dict) -> dict:
        """
        Partially update an existing TV channel by its ID.

        Args:
            channel_id (str): The ID of the TV channel to update.
            data (dict): A dictionary containing the fields to update.

        Returns:
            dict: The updated TV channel data as a dictionary.

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

    @staticmethod
    def get_channels_by_genre(genre_id: str) -> list:
        """
        Retrieve TV channels associated with a specific genre.

        Args:
            genre_id (str): The ID of the genre to filter TV channels by.

        Returns:
            List: A list of TV channel data as dictionaries associated with the specified genre.

        Example:
            channels_in_action_genre = ChannelService.get_channels_by_genre('12345')
        """
        # Query the database to retrieve TV channels associated with the specified genre
        channels = Channel.objects(genre=genre_id)
        # Convert the matching TV channel objects to a list of dictionaries
        channel_data_list = [channel.to_dict() for channel in channels]
        # Return the list of TV channel data as dictionaries
        return channel_data_list

    @staticmethod
    def count_active_channels() -> int:
        """
        Count the total number of active TV channels in the database.

        Returns:
            int: The total count of active TV channels.

        Example:
            active_channel_count = ChannelService.count_active_channels()
        """
        # Use the .count() method to count the number of active TV channels in the database
        return Channel.objects(is_active=True).count()

    @staticmethod
    def count_inactive_channels() -> int:
        """
        Count the total number of inactive TV channels in the database.

        Returns:
            int: The total count of inactive TV channels.

        Example:
            inactive_channel_count = ChannelService.count_inactive_channels()
        """
        # Use the .count() method to count the number of inactive TV channels in the database
        return Channel.objects(is_active=False).count()

    @staticmethod
    def toggle_channel_active_status(channel_id: str) -> bool:
        """
        Toggle the active status of a TV channel.

        Args:
            channel_id (str): The ID of the TV channel to toggle.

        Returns:
            bool: True if the status was successfully toggled, False otherwise.

        Example:
            status_toggled = ChannelService.toggle_channel_active_status('12345')
        """
        # Retrieve the existing TV channel data by its ID using the get_channel_by_id function
        channel = ChannelService.get_channel_by_id(channel_id)

        # Check if the TV channel exists
        if channel:
            # Toggle the active status
            channel.toggle_active_status()
            return True
        else:
            # Return False if the TV channel with the specified ID doesn't exist.
            return False

    @staticmethod
    def get_channels_by_country(country: str) -> list:
        """
        Retrieve TV channels based on the country they are from.

        Args:
            country (str): The country to filter TV channels by.

        Returns:
            List: A list of TV channel data as dictionaries from the specified country.

        Example:
            channels_in_usa = ChannelService.get_channels_by_country('USA')
        """
        # Query the database to retrieve TV channels from the specified country
        channels = Channel.objects(country=country)
        # Convert the matching TV channel objects to a list of dictionaries
        channel_data_list = [channel.to_dict() for channel in channels]
        # Return the list of TV channel data as dictionaries
        return channel_data_list

    @staticmethod
    def get_channels_by_language(language: str) -> list:
        """
        Retrieve TV channels based on the language they use.

        Args:
            language (str): The language to filter TV channels by.

        Returns:
            List: A list of TV channel data as dictionaries using the specified language.

        Example:
            channels_in_english = ChannelService.get_channels_by_language('English')
        """
        # Query the database to retrieve TV channels using the specified language
        channels = Channel.objects(language=language)
        # Convert the matching TV channel objects to a list of dictionaries
        channel_data_list = [channel.to_dict() for channel in channels]
        # Return the list of TV channel data as dictionaries
        return channel_data_list

    @staticmethod
    def get_hd_channels() -> list:
        """
        Retrieve TV channels that are available in high definition (HD).

        Returns:
            List: A list of TV channel data as dictionaries for HD channels.

        Example:
            hd_channels = ChannelService.get_hd_channels()
        """
        # Query the database to retrieve TV channels that are available in HD
        channels = Channel.objects(is_hd=True)
        # Convert the matching TV channel objects to a list of dictionaries
        channel_data_list = [channel.to_dict() for channel in channels]
        # Return the list of TV channel data as dictionaries
        return channel_data_list

    @staticmethod
    def search_channels(query: str) -> list:
        """
        Search for TV channels based on a query string in 'name,' 'description,' 'language,' or 'country' fields.

        Args:
            query (str): The search query.

        Returns:
            List: A list of matching TV channel data as dictionaries.

        Example:
            matching_channels = ChannelService.search_channels('news')
        """
        # Create a query using the `$or` operator to search in 'name,' 'description,' 'language,' and 'country' fields
        channels = Channel.objects(
            Q(name__icontains=query) | Q(description__icontains=query) | Q(language__icontains=query) | Q(country__icontains=query)
        )

        # Convert the matching TV channel objects to a list of dictionaries
        return [channel.to_dict() for channel in channels]
    
    @staticmethod
    def toggle_hd_status(channel_id: str) -> bool:
        """
        Toggle the high-definition (HD) status of a TV channel.

        Args:
            channel_id (str): The ID of the TV channel to toggle HD status for.

        Returns:
            bool: True if the HD status was successfully toggled, False otherwise.

        Example:
            hd_status_toggled = ChannelService.toggle_hd_status('12345')
        """
        # Retrieve the existing TV channel data by its ID using the get_channel_by_id function
        channel = ChannelService.get_channel_by_id(channel_id)

        # Check if the TV channel exists
        if channel:
            # Toggle the HD status
            channel.is_hd = not channel.is_hd
            channel.save()
            return True
        else:
            # Return False if the TV channel with the specified ID doesn't exist.
            return False

    @staticmethod
    def deactivate_channel(channel_id: str) -> bool:
        """
        Deactivate a TV channel by setting its 'is_active' status to False.

        Args:
            channel_id (str): The ID of the TV channel to deactivate.

        Returns:
            bool: True if the channel was successfully deactivated, False otherwise.

        Example:
            channel_deactivated = ChannelService.deactivate_channel('12345')
        """
        # Retrieve the existing TV channel data by its ID using the get_channel_by_id function
        channel = ChannelService.get_channel_by_id(channel_id)

        # Check if the TV channel exists
        if channel:
            # Deactivate the channel
            channel.is_active = False
            channel.save()
            return True
        else:
            # Return False if the TV channel with the specified ID doesn't exist.
            return False
        
    @staticmethod
    def delete_all_channels() -> int:
        """
        Delete all TV channels in the database.

        Returns:
            int: The number of TV channels deleted.

        Example:
            deleted_count = ChannelService.delete_all_channels()
        """
        # Query the database to retrieve all TV channels
        channels = Channel.objects()
        # Initialize a count for deleted TV channels
        deleted_count = 0
        # Iterate through the list of TV channels and delete each one
        for channel in channels:
            # Delete the TV channel from the database
            channel.delete()
            deleted_count += 1
        # Return the total count of deleted TV channels
        return deleted_count

    @classmethod
    def get_channels_by_name(cls, name):
        """
        Get a list of channels that match the given name.

        Args:
            name (str): The name to search for.

        Returns:
            list: A list of channel documents matching the name.
        """ 
        # Query the database to retrieve channels with the specified name
        channels = Channel.objects(name__icontains=name)
        # Convert the matching channel objects to a list of dictionaries
        return channels