import typing
import sqlalchemy
import sqlalchemy.engine.base

from .app_route import app_route_database
from .product import product 
from .product_loader import product_loader
from .exception import bad_request_exception
from .exception import not_found_exception
from .exception import access_denied_exception
from .exception import database_exception
from .exception import incorrect_target_exception
from .users_collection import users_collection
from .reservation import reservation 
from .reservation import reservation_factory
from .reservation_loader import reservation_loader
from .deep_request import deep_request
from .reservations_collection import reservations_collection
from .reservations_response import reservations_response

class reservation_deep_request(deep_request):
    @property
    def has_product(self) -> bool:  
        return self.target_barcode is not None or self.target_name is not None

    @property 
    def has_email(self) -> bool:
        return self.email is not None

    @property
    def has_phone_number(self) -> bool:
        return self.phone_number is not None

    @property
    def has_apikey(self) -> bool:
        return self.apikey is not None

class reservation_app(app_route_database):
    def __init__(
        self,
        connection: sqlalchemy.engine.base.Engine,
        users: users_collection
    ) -> None:
        super().__init__(connection)
        self.__users_collection = users

    def get_by_user(self, params: dict) -> dict:
        request = reservation_deep_request(params)
        
        try:
            with self.__reservation_loader as loader:
                collection = self.__load_by_user(request, loader)
                self.__reservation_user_filter(collection, request)

                reservations_list = reservations_response() \
                    .collection(collection.results())

                return self._success(reservations = reservations_list)
        
        except Exception as error:
            return self.__catch(error)

    def get_by_product(self, params: dict) -> dict:
        request = reservation_deep_request(params)

        try:
            self.__login(request)
            item = self.__load_product(request)
            
            with self.__reservation_loader as loader:
                collection = loader.get_by_target(item) 
                self.__reservation_user_filter(collection, request)
 
                reservations_list = reservations_response() \
                    .collection(collection.results())

                return self._success(reservations = reservations_list)
        
        except Exception as error:
            return self.__catch(error)

    def rent_product(self, params: dict) -> dict:
        request = reservation_deep_request(params)
        
        try:
            self.__login(request)
            item = self.__load_product(request)
            rent = self.__create_factory(request).target(item).result()
            
            with self.__reservation_loader as loader:
                if not self.__product_avairable(item):
                    raise bad_request_exception("Item already not avairable") 

                if not loader.store(rent):
                    raise database_exception()

            return self._success()

        except Exception as error:
            return self.__catch(error)

    def give_back_product(self, params: dict) -> dict:
        request = reservation_deep_request(params)

        try:
            self.__login(request)
            target = self.__get_reservation(request)

            with self.__reservation_loader as loader:
                if loader.drop(target):
                    return self._success()
                
                raise database_exception()
        
        except Exception as error:
            return self.__catch(error)

    def __reservation_user_filter(
        self, 
        collection: reservations_collection, 
        request: deep_request
    ) -> bool:
        if not request.has_email and not request.has_phone_number:
            return False

        if request.has_email:
            collection.by_email(request.email)

        if request.has_phone_number:
            collection.by_phone_number(request.phone_number)

        return True

    def __reservation_product_filter(
        self,
        collection: reservations_collection,
        request: deep_request
    ) -> bool:
        if not request.has_product:
            return False

        item = self.__load_product(request)
        collection.by_target(item)

        return True


    def __get_reservation(self, request: deep_request) -> reservation:
        with self.__reservation_loader as loader:
            item = self.__load_product(request)
            collection = loader.get_by_target(item)
            
            if not self.__reservation_user_filter(collection, request):
                raise bad_request_exception("Not contaim email or phone")

            result = collection.results()

            if len(result) == 0:    
                raise not_found_exception()
            
            if len(result) > 1:
                raise incorrect_target_exception("Found too much targets.")

            return result[0]

    def __load_by_user(
        self, 
        request: deep_request,
        loader: reservation_loader
    ) -> reservations_collection:
        if request.has_email:
            return loader.get_by_email(request.email)

        if request.has_phone_number:
            return loader.get_by_phone_number(request.phone_number)

        raise bad_request_exception("Has no email or phone number")

    def __create_factory(self, request: deep_request) -> reservation_factory:
        if not request.has_email and not request.has_phone_number:
            raise bad_request_exception("Not contain email or/and phone")

        factory = reservation_factory()

        if request.has_email:
            factory.email(request.email)

        if request.has_phone_number:
            factory.phone_number(request.phone_number)

        return factory

    def __login(self, request: deep_request) -> None:
        if not request.has_apikey:
            raise access_denied_exception()

        if self.__users_collection.get(request.apikey):
            return 

        raise access_denied_exception()

    def __load_product(self, request: deep_request) -> product:  
        if request.target_barcode is not None:
            return self.__product_by_barcode(request)

        if request.target_name is not None:
            return self.__product_by_name(request)

        raise bad_request_exception("Not contain barcode or name")

    def __product_by_barcode(self, request: deep_request) -> product:
        with self.__product_loader as loader:
            product = loader.get_by_barcode(request.target_barcode)

            if product is not None:
                return product

            raise not_found_exception()

    def __product_avairable(self, target: product) -> bool:
        with self.__product_loader as loader:
            return loader.get_by_barcode(target.barcode).on_stock > 0

    def __product_by_name(self, request: deep_request) -> product:
        with self.__product_loader as loader:
            product = loader.get_by_name(request.target_name) 

            if product is not None:
                product.on_stock
                return product
            
            raise not_found_exception()

    def __catch(self, error: Exception) -> dict:
        return self._fail(str(error))

    @property
    def __reservation_loader(self):
        return reservation_loader(self._connection)

    @property
    def __product_loader(self):
        return product_loader(self._connection)
        
