import typing
import sqlalchemy
import sqlalchemy.engine.base

from .app_route import app_route_database
from .product import product 
from .product import product_factory
from .product_loader import product_loader
from .product_response import product_response
from .product_builder import product_builder
from .exception import bad_request_exception
from .exception import not_found_exception
from .exception import access_denied_exception
from .users_collection import users_collection
from .image import image
from .directory_image import directory_image

class product_app(app_route_database):
    def __init__(
        self, 
        connection: sqlalchemy.engine.base.Engine,
        users: users_collection,
        images: directory_image

    ) -> None:
        super().__init__(connection)
        self.__users_collection = users
        self.__response = product_response(images)
        self.__images_manager = images

    def all(self) -> dict:
        try:
            with self.__products_database as loader:
                return self.__collection(loader.load_all())

        except Exception as error:
            return self._fail(str(error))
    
    def get_barcode(self, target: str) -> dict:
        try:
            with self.__products_database as loader:
                return self.__single(loader.get_by_barcode(target))

        except Exception as error:
            return self._fail(str(error))

    def get_name(self, target: str) -> dict:
        try:
            with self.__products_database as loader:
                return self.__single(loader.get_by_name(target))

        except Exception as error:
            return self._fail(str(error))

    def search_name(self, target: str) -> dict:
        try:
            with self.__products_database as loader:
                return self.__collection(loader.search_by_name(target))

        except Exception as error:
            return self._fail(str(error))

    def search_author(self, target: str) -> dict:
        try:
            with self.__products_database as loader:
                return self.__collection(loader.search_by_author(target))

        except Exception as error:
            return self._fail(str(error))

    def check_barcode(self, target: str) -> dict:
        try:
            with self.__products_database as loader:
                return self.__exists(loader.barcode_in_use(target))

        except Exception as error:
            return self._fail(str(error))

    def check_name(self, target: str) -> dict:
        try:
            with self.__products_database as loader:
                return self.__exists(loader.name_in_use(target))

        except Exception as error:
            return self._fail(str(error))

    def __image(self, send: dict) -> image | None:
        if not "image" in send:
            return None

        return image(send["image"])

    def create(self, send: dict) -> dict:
        try:
            if not self.__logged_in(send):
                raise access_denied_exception()
                
            target = product_builder().modify(send).result
            image = self.__image(send)

            if image is None:
                raise bad_request_exception("not contain image")

            with self.__products_database as loader:
                result = loader.store(target)
               
                if not result:
                    return self.__modify(result, "Can not create product.")
               
                try:
                    self.__images_manager.save(image, target)
                except Exception as error:
                    loader.drop(target)
                    raise error

                return self._success()
                

        except Exception as error:
            return self._fail(str(error))

    def __logged_in(self, send: dict) -> bool:
        if not "apikey" in send:
            return False

        return self.__users.get(send["apikey"]) is not None

    def __select_by_sended(
        self, 
        send: dict, 
        loader: product_loader
    ) -> product | None:
        barcode = None
        name = None

        if "target_barcode" in send:
            barcode = send["target_barcode"]

        if "target_name" in send:
            name = send["target_name"]

        if barcode is not None and name is not None:
            content = "Give only one, target_name or target_barcode"
            raise bad_request_exception(content)

        if barcode is None and name is None:
            content = "Give target_barcode or target_name"
            raise bad_request_exception(content)

        result = None

        if barcode is not None:
            result = loader.get_by_barcode(barcode)

        if name is not None:
            result = loader.get_by_name(name)
        
        if result is None:
            raise not_found_exception()

        return result

    def update(self, send: dict) -> dict:
        try:
            if not self.__logged_in(send):
                raise access_denied_exception()

            with self.__products_database as loader:
                target = self.__select_by_sended(send, loader)
                
                old = target.save_copy()
                updated = product_builder(target).modify(send).result
                
                if not loader.store(updated):
                    return self._fail("Can not update product.")
                
                try:
                    self.__images_manager.update(old, updated)
                except Exception as error:
                    updated.restore_copy(old)
                    
                    if not loader.store(updated):
                        error = "Image caould be moved. Can not restore "
                        error = error + "previous product. Fatal error."

                        return self._fail(error)

                    raise error
                
                return self._success()

        except Exception as error:
            return self._fail(str(error))

    def update_image(self, send: dict) -> dict:
        try:
            if not self.__logged_in(send):
                raise access_denied_exception()

            image = self.__image(send)

            if image is None:
                raise bad_request_exception("Not found image in request.")
            
            with self.__products_database as loader:
                target = self.__select_by_sended(send, loader)
                
            self.__images_manager.drop(target)
            self.__images_manager.save(image, target)

            return self._success()
    
        except Exception as error:
            return self._fail(str(error))

    def delete(self, send: dict) -> dict:
        try:
            if not self.__logged_in(send):
                raise access_denied_exception()

            with self.__products_database as loader:
                target = self.__select_by_sended(send, loader)
                copy = target.save_copy()
                
                if not loader.drop(target):
                    return self._fail("Can not delete product.")
                
            self.__images_manager.drop(copy)

            return self._success()

        except Exception as error:
            return self._fail(str(error))

    def __modify(self, result: bool, cause: str) -> dict:
        if result:
            return self._success()
        else:
            return self._fail(cause)

    def __exists(self, result: bool) -> dict:
        return self._success(exists = result)

    def __single(self, target: product) -> dict:
        if target is None:
            return self._fail("Can not found product in database.")

        return self._success(product = self.__response.single(target))

    def __collection(self, target: typing.Iterable[product]) -> dict:
        return self._success(collection = self.__response.collection(target))

    @property
    def __users(self) -> users_collection:
        return self.__users_collection

    @property
    def __products_database(self) -> product_loader:
        return product_loader(self._connection)
