"""
product.py contains the Product service class.
"""
from abc import ABC
from typing import Generator
from typing import List

import gshoppy.constants as constants
from gshoppy.services.core import Service
from gshoppy.services.models import Product
from gshoppy.utils import get_logger
from gshoppy.utils import get_product_id
from gshoppy.utils import setup_logging

setup_logging()
logger = get_logger()


class ProductService(Service, ABC):
    """
    The Product service class.
    """

    def _init_service(self):
        """
        Initializes the Product Service.
        :return:
        """

        logger.info("Initializing the Product service.")

        self._service = self.base_service.products()

    def get(self, offer_id: str) -> Product:
        """
        Gets a product.
        :param offer_id: The offer ID of the product to get.
        :return:
        """

        logger.info("Getting product.")

        # Get the product ID.
        product_id = get_product_id(offer_id)

        # Build a request.
        request = self.service.get(merchantId=self.merchant_id, productId=product_id)

        # Execute the request.
        response = request.execute()

        return Product(**response)

    def list(self) -> Generator:
        """
        Yield out pages of products.
        :return:
        """

        logger.info("Fetching products")

        # Build a request.
        request = self.service.list(
            merchantId=self.merchant_id, maxResults=constants.MAX_PAGE_SIZE
        )

        # Get all pages.
        while request is not None:
            # Execute the request.
            response = request.execute()

            # Yield the products.
            resources = response.get("resources")
            for resource in resources:
                yield Product(**resource)

            # Update the request.
            request = self.service.list_next(
                previous_request=request, previous_response=response
            )

    def insert(self, product: Product) -> Product:
        """
        Inserts a product.
        :param product: The product to insert.
        :return:
        """

        logger.info("Inserting product.")

        # Get the request body.
        body = product.model_dump()
        if "source" in body:
            del body["source"]

        # Build a request.
        request = self.service.insert(merchantId=self.merchant_id, body=body)

        # Execute the request.
        response = request.execute()
        return Product(**response)

    def update(self, offer_id: str, **product) -> Product:
        """
        Updates a product.
        :param offer_id:
        :param product:
        :return:
        """

        logger.info("Updating product.")

        # Get the product.
        _product = self.get(offer_id)

        # Update the product.
        product_dict = _product.model_dump()
        product_dict.update(product)
        _product = Product(**product_dict)

        # Cannot specify `source` when inserting a product.
        # del _product["source"]

        # Re-insert the product.
        return self.insert(_product)

    def delete(self, offer_id: str) -> None:
        """
        Deletes a product.
        :param offer_id: The offer ID of the product to delete.
        :return:
        """

        logger.info("Deleting product.")

        # Get the product ID.
        product_id = get_product_id(offer_id)

        # Build a request.
        request = self.service.delete(merchantId=self.merchant_id, productId=product_id)

        # Execute the request.
        request.execute()

    def batch_insert(
        self, *products: List[dict], batch_size: int = constants.BATCH_SIZE
    ):
        """
        Inserts products in batches.
        :param products:
        :param batch_size:
        :return:
        """

        raise NotImplementedError()

    def batch_delete(
        self, *offer_ids: List[str], batch_size: int = constants.BATCH_SIZE
    ):
        """
        Deletes products in batches.
        :param offer_ids:
        :param batch_size:
        :return:
        """

        raise NotImplementedError()
