from mongoengine.queryset import Q

from app.models.product import Product
from app.services.brand_service import BrandService
from app.services.mapper_service import IDMapper, ExistChecker
from app.models.brand import Brand
from app.models.spot import Spot


class ProductService:
    """
    Service class for creating and retrieving products.

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

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

        get_product_by_id(product_id: str) -> dict:
            Retrieves a single product by its ID and returns its data as a dictionary.

        update_product(product_id: str, data: dict) -> dict:
            Updates an existing product and returns its updated data as a dictionary.

        delete_product(product_id: str) -> bool:
            Deletes a product by its ID and returns True if successful, False otherwise.

        search_products(query: str) -> list[dict]:
            Searches for products based on a query string in 'name' or other fields.

        update_product_field(product_id: str, field_name: str, new_value) -> dict:
            Update a specific field of an existing product by its ID.

        product_exists(name: str) -> bool:
            Check if a product with a specific name exists in the database.
    """

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

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

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

        Example:
            # Example data for creating a product
            product_data = {
                'name': 'Example Product',
                'description': 'Product Description',
                # Add other product fields here
            }

            # Create a new product using the ProductService
            new_product_data = ProductService.create_product(product_data)
        """
        # Create a new Product object with the provided data

        # Save the newly created product to the database
        brand = BrandService.get_brand_by_id(data['brand'])
        product = Product(**data)
        product.brand = brand.to_dict()
        product.save()
        # Convert the product object to a dictionary representation
        return product.to_dict()

    @staticmethod
    def get_all_products() -> list:
        """
        Retrieve all products from the database.

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

        Example:
            all_products_data = ProductService.get_all_products()
        """
        # Query the database to retrieve all product objects
        products = Product.objects()
        # Convert each product object to a dictionary representation and create a list of them
        product_data_list = [product.to_dict() for product in products]
        # Return the list of product data as dictionaries
        return product_data_list

    @staticmethod
    def get_product_by_id(product_id: str) -> dict:
        """
        Retrieve a single product by its ID.

        Args:
            product_id (str): The ID of the product to retrieve.

        Returns:
            dict: The product data as a dictionary.

        Example:
            product_data = ProductService.get_product_by_id('12345')
        """
        # Map the public ID to the internal ID for the Product model
        product_id = IDMapper.public_id_mapper(product_id, Product)
        # Check if the mapping was successful and if an internal ID exists
        if not product_id:
            return None
        # Query the database to retrieve the product by its internal ID
        product = Product.objects(id=product_id).first()
        # Convert the product object to a dictionary representation
        return product

    @staticmethod
    def get_product_by_name(product_name: str) -> dict:
        """
        Retrieve a single product by its name.

        Args:
            product_name (str): The name of the product to retrieve.

        Returns:
            dict: The product data as a dictionary.

        Example:
            product_data = ProductService.get_product_by_name('12345')
        """
        # Map the public ID to the internal ID for the Product model
        product_name = IDMapper.public_id_mapper(product_name, Product)
        # Check if the mapping was successful and if an internal ID exists
        if not product_name:
            return None
        # Query the database to retrieve the product by its internal ID
        product = Product.objects(id=product_name).first()
        # Convert the product object to a dictionary representation
        return product.to_dict()
    
    @staticmethod
    def update_product(product_id: str, data: dict) -> dict:
        """
        Update an existing product by its ID.

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

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

        Example:
            updated_product_data = ProductService.update_product('12345', {'name': 'Updated Product Name', 'description': 'Updated Description'})
        """
        # Retrieve the existing product data by its ID using the get_product_by_id function
        product = ProductService.get_product_by_id(product_id)

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

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

        Returns:
            int: The total count of product.

        Example:
            total_genre_count = ProductService.count_product()
        """
        # Use the .count() method to count the number of product in the database
        return Product.objects().count()


    @staticmethod
    def delete_all_products():
        products = Product.objects()
        deleted_count = 0 
        # Iterate through the list of products and delete each one
        for product in products:
            # Delete the genre from the database
            product.delete()
            deleted_count += 1
        # Return the total count of deleted products
        return deleted_count

    @staticmethod
    def delete_product(product_id: str) -> bool:
        """
        Delete a product by its ID.

        Args:
            product_id (str): The ID of the product to delete.

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

        Example:
            result = ProductService.delete_product('12345')
        """
        # Retrieve the existing product data by its ID using the get_product_by_id function
        product = ProductService.get_product_by_id(product_id)

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

    @staticmethod
    def search_products(query: str) -> list:
        """
        Search for products based on a query string in 'name' or other fields.

        Args:
            query (str): The search query.

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

        Example:
            matching_products = ProductService.search_products('example')
        """
        # Create a query to search in 'name' or other relevant fields
        products = Product.objects(Q(name__icontains=query) | Q(description__icontains=query))
        # Convert the matching product objects to a list of dictionaries
        return [product.to_dict() for product in products]

    @staticmethod
    def update_product_field(product_id: str, field_name: str, new_value) -> dict:
        """
        Update a specific field of an existing product by its ID.

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

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

        Example:
            updated_product_data = ProductService.update_product_field('12345', 'name', 'Updated Product Name')
        """
        # Retrieve the existing product data by its ID using the get_product_by_id function
        product = ProductService.get_product_by_id(product_id)

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

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

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

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

        Example:
            exists = ProductService.product_exists('example')
        """
        # Query the database to check if a product with the specified name exists
        return ExistChecker.exist(name, Product)


    @staticmethod
    def get_products_by_brand(brand_id: str) -> list:
        """
        Retrieve all products associated with a specific brand.

        Args:
            brand_id (str): The ID of the brand to retrieve products for.

        Returns:
            List: A list of product data associated with the brand as dictionaries.

        Example:
            brand_products = ProductService.get_products_by_brand('12345')
        """
        # Use the BrandService to get the brand data
        brand_data = BrandService.get_brand_by_id(brand_id)
        
        if brand_data:
            # Get the internal brand ID
            internal_brand_id = brand_data.id 
            # Query the database to retrieve all products associated with the brand
            products = Product.objects(brand=internal_brand_id)
            # Convert each product object to a dictionary representation and create a list of them
            product_data_list = [product.to_dict() for product in products]
            # Return the list of product data as dictionaries
            return product_data_list
        else:
            return []  # Brand not found