import sqlmodel

from .exception import validator_exception
from .validator import name_validator
from .validator import description_validator
from .validator import barcode_validator
from .validator import author_validator

class product(sqlmodel.SQLModel, table = True):
    """ 
    This class is product model in database. Product is item, which could be 
    shared.
    """

    """ Name of the table to use in foreign keys. """
    __tablename__: str = "product"

    """ ID of the product in the database. """
    id: int | None = sqlmodel.Field(default = None, primary_key = True)

    """ Name of the product, or title of the book. """
    name: str | None = sqlmodel.Field(
        default = None, 
        index = True, 
        unique = True
    )

    """ Description of the product, provided by creator. """
    description: str | None = sqlmodel.Field(default = None)

    """ Author of the product. """
    author: str | None = sqlmodel.Field(default = None)

    """ Count of instances this products in stock. """
    stock_count: int = sqlmodel.Field(default = 0)

    """ This is barcode (EAN) of the product. """
    barcode: str | None = sqlmodel.Field(
        default = None, 
        index = True, 
        unique = True
    )

    reservations: list["reservation"] = sqlmodel.Relationship(
        back_populates = "target"
    )

    @property
    def on_stock(self) -> int:
        current = self.stock_count - len(self.reservations)
        
        if current < 0:
            return 0

        return current
    
    def save_copy(self) -> object:
        """
        This return clone of the product, which is not connect with 
        database.

        Returns:
            (product): Clone of the product, which is not database connected
        """

        result = product()
        result.name = self.name
        result.description = self.description
        result.author = self.author
        result.stock_count = self.stock_count
        result.barcode = self.barcode

        return result

    def restore_copy(self, target: object) -> object:
        """
        This restore content of the product, from the save copy.

        Parameters:
            target (product): Target product to restore

        Returns:
            (product): Return product itself
        """

        self.name = target.name
        self.description = target.description
        self.author = target.author
        self.stock_count = target.stock_count
        self.barcode = target.barcode

        return self

    @property
    def in_database(self) -> bool:
        """ This return True when product exists in database. """

        return self.id is not None

    @property
    def avairable(self) -> bool:
        """ This check that item is avairable on stock. """

        return self.stock_count > 0

    def __str__(self) -> str:
        """
        This dump product to string, which is helpfull when debug.

        Returns:
            (str): Product as string dump
        """

        content = str(self.name) + " "

        if self.in_database:
            content = content + "#" + str(self.id)

        content = content + "\n"
        content = content + "Description: " + str(self.description) + "\n"
        content = content + "Author: " + str(self.author) + "\n"
        content = content + "Barcode (EAN): " + str(self.barcode) + "\n"
        content = content + "In stock: " + str(self.stock_count) + "\n"

        return content

    @property
    def ready(self) -> bool:
        """ Check that product is ready to insert into database. """

        if self.name is None or self.description is None: 
            return False

        if self.barcode is None or self.author is None:
            return False

        return True
        
        
class product_factory:
    """
    This is builder of the product. It helps to handle validation of the 
    product params, and modify exists product with validators.
    """

    def __init__(self, target: product | None = None) -> None:
        """
        This create new builder. When get already created item, it work on 
        given once. When receive None, create new product to work on.

        Parameters:
            target (product | None): Item to work on (default: None)
        """
        
        if target is not None:
            self.__target = target
            return

        self.__target = product()

    @property
    def ready(self) -> bool:
        """ It return True when target is ready to insert into database. """

        return self.__target.ready

    @property
    def result(self) -> product:
        """ It return ready product, or raise Exception if not Ready yet. """

        if not self.__target.ready:
            raise Exception("Product in builder is not ready yet.")

        return self.__target

    @property
    def name(self) -> str | None:
        """ It return name of the product. """

        return self.__target.name

    @name.setter
    def name(self, target: str) -> None:
        """ It set name of the product, and validating it. """

        if name_validator(target).invalid:
            raise validator_exception("product.name")

        self.__target.name = target

    @property
    def description(self) -> str | None:
        """ This return product description. """

        return self.__target.description

    def add_to_stock(self) -> None:
        """
        This add one item to stock.
        """

        self.__target.stock_count += 1

    def get_from_stock(self) -> bool:
        """
        This get single item from stock.

        Returns:
            (bool): True when get success, false when not avairable        
        """

        if not self.__target.avairable:
            return False

        self.__target.stock_count -= 1
        return True

    @description.setter
    def description(self, target: str) -> None:
        """ This set description of the product, and validate it."""

        if description_validator(target).invalid:
            raise validator_exception("product.description")

        self.__target.description = target    

    @property
    def barcode(self) -> str | None:
        """ It return barcode of building product. """

        return self.__target.barcode

    @barcode.setter
    def barcode(self, target: str) -> None:
        """ This set new barcode, and validate it. """

        if barcode_validator(target).invalid:
            raise validator_exception("product.barcode")

        self.__target.barcode = target

    @property
    def author(self) -> str | None:
        """ This return author of the product. """

        return self.__target.author

    @author.setter
    def author(self, target: str) -> None:
        """ This validate author of the product, and set it. """

        if author_validator(target).invalid:
            raise validator_exception("product.author")

        self.__target.author = target

    @property
    def stock_count(self) -> int:
        """ This return how much product is in stock. """

        return self.__target.stock_count

    @stock_count.setter
    def stock_count(self, target: int) -> None:
        """ This set stock count of the product, it mut be positive number. """

        if target < 0:
            raise validator_exception("product.stock_count")

        self.__target.stock_count = target
