class VendorRatingController(BaseController):
    def __init__(self, request):
        BaseController.__init__(self, request)
        self.vendor_rating_repo = VendorRatingRepo()
        self.vendor_repo = VendorRepo()
        self.vendor_engagement_repo = VendorEngagementRepo()
        self.menu_repo = MenuRepo()
        self.meal_repo = MealItemRepo()
        self.order_repo = OrderRepo()

    def list_ratings(self, date):
        """retrieves a list of all ratings"""
        query_kwargs = self.get_params_dict()
        date = datetime.strptime(date, '%Y-%m-%d')

        ratings = self.vendor_rating_repo.filter_by(service_date=date,
                                                    **query_kwargs)
        if ratings.items:
            result = []
            vendor_name = self.vendor_repo.get(ratings.items[0].vendor_id).name

            for rating in ratings.items:
                if not rating.main_meal_id:
                    continue

                meal_name = self.meal_repo.get(rating.main_meal_id).name

                if meal_name not in [item.get('mainMeal') for item in result]:
                    meal_rating = {
                        'mainMeal':
                        meal_name,
                        'overallRating':
                        self.vendor_rating_repo.meal_average(
                            rating.main_meal_id, date),
                        'items': [
                            rtng.serialize() for rtng in ratings.items
                            if rtng.main_meal_id == rating.main_meal_id
                        ]
                    }
                    result.append(meal_rating)

            return self.handle_response('OK',
                                        payload={
                                            'date': date,
                                            'vendor': vendor_name,
                                            'result': result
                                        })
        return self.handle_response('No ratings for this date',
                                    status_code=404)

    def get_vendor_rating(self, rating_id):
        """retrieves the details of a specific rating, giving the rating id"""
        rating = self.vendor_rating_repo.get(rating_id)
        if rating:
            rtng = rating.serialize()

            return self.handle_response('OK', payload={'rating': rtng})
        else:
            return self.handle_response('Bad Request', status_code=400)

    def create_vendor_rating(self):
        '''Adds a vendor rating during a specific engagement'''
        (comment, rating, service_date, channel,
         engagement_id) = self.request_params('comment', 'rating',
                                              'serviceDate', 'channel',
                                              'engagementId')
        main_meal_id = None
        user_id = Auth.user('id')
        vendor_id = self.vendor_engagement_repo.get(engagement_id).vendor_id

        if self.vendor_repo.get(vendor_id):

            service_date = datetime.strptime(service_date, '%Y-%m-%d')

            rating = self.vendor_rating_repo.new_rating(
                vendor_id, user_id, rating, service_date,
                RatingType.engagement, engagement_id, engagement_id,
                main_meal_id, channel, comment)
            self.vendor_repo.update_vendor_average_rating(vendor_id)
            rtng = rating.serialize()

            return self.handle_response('Rating created',
                                        payload={'rating': rtng},
                                        status_code=201)

        return self.handle_response('Invalid vendor_id provided',
                                    status_code=400)

    def create_order_rating(self):
        """Adds a order or meal rating during a specific engagement """

        (order_id, main_meal_id, engagement_id, comment, rating, service_date,
         channel) = self.request_params('orderId', 'mainMealId',
                                        'engagementId', 'comment', 'rating',
                                        'serviceDate', 'channel')
        if not (1 <= rating <= 5):
            return self.handle_response(
                'Rating must be between 1 and 5, inclusive.', status_code=400)

        user_id = Auth.user('id')
        if not self.meal_repo.get(main_meal_id):
            return self.handle_response('Meal item with this id not found',
                                        status_code=400)
        engagement = self.vendor_engagement_repo.get(engagement_id)
        if not engagement:
            return self.handle_response('Engagement with this id is not found',
                                        status_code=400)
        vendor_id = engagement.vendor_id
        if order_id:
            rating_type = RatingType.order
            type_id = order_id
            order = self.order_repo.get(order_id)
            if not order:
                return self.handle_response('Order with this id is not found',
                                            status_code=400)
            if order.has_rated:
                return self.handle_response('This order has been rated',
                                            status_code=400)

        else:
            if (datetime.now() -
                    datetime.strptime(service_date, '%Y-%m-%d')).days < 1:
                return self.handle_response(
                    'You can only rate meals of past days.', status_code=400)
            rating_type = RatingType.meal
            type_id = main_meal_id
            user_meal_rating = self.vendor_rating_repo.get_unpaginated(
                user_id=user_id, type_id=type_id, rating_type='meal')
            if user_meal_rating:
                return self.handle_response('You have already rated this meal',
                                            status_code=400)

        rating = self.vendor_rating_repo.new_rating(
            vendor_id, user_id, rating,
            datetime.strptime(service_date, '%Y-%m-%d'), rating_type, type_id,
            engagement_id, main_meal_id, channel, comment)

        self.vendor_repo.update_vendor_average_rating(vendor_id)
        if rating.id and rating_type == RatingType.order:
            updates = {'has_rated': True}
            self.order_repo.update(order, **updates)

        rating_obj = rating.serialize()
        return self.handle_response('Rating successful',
                                    payload={'rating': rating_obj},
                                    status_code=201)

    def update_vendor_rating(self, rating_id):
        """edits an existing rating"""

        rtng = self.vendor_rating_repo.get(rating_id)
        comment = self.get_json()['comment']
        if rtng:
            if Auth.user(
                    'id'
            ) == rtng.user_id:  #You cannot update someone else's rating

                updates = {}
                if comment:
                    updates['comment'] = comment
                self.vendor_rating_repo.update(rtng, **updates)
                return self.handle_response(
                    'OK', payload={'rating': rtng.serialize()})
            return self.handle_response(
                'You are not allowed to update a rating that is not yours',
                status_code=403)
        return self.handle_response('Invalid or incorrect rating_id provided',
                                    status_code=404)
class OrderController(BaseController):

    default_meal_item_return_fields = ['name', 'image', 'id', 'meal_type']

    def __init__(self, request):
        BaseController.__init__(self, request)
        self.order_repo = OrderRepo()
        self.meal_item_repo = MealItemRepo()
        self.andela_service = AndelaService()
        self.rating_repo = VendorRatingRepo()

    def list_orders(self):
        """
		List all orders in the application: should rarely be should
		:return:
		"""
        location_id = Auth.get_location()
        yesterday = date.today() - timedelta(days=1)
        tomorrow = date.today() + timedelta(days=1)

        orders = self.order_repo.get_range_paginated_options_all(
            start_date=yesterday, end_date=tomorrow, location_id=location_id)
        order_list = []
        if len(orders.items) > 0:
            for order in orders.items:
                meal_items = self.order_repo.get(order.id).meal_item_orders
                try:
                    user = self.andela_service.get_user_by_email_or_id(
                        order.user_id)
                except Exception as e:
                    return str(e)

                order_item = order.serialize()
                order_item['mealItems'] = [
                    item.to_dict(
                        only=OrderController.default_meal_item_return_fields)
                    for item in meal_items
                ]

                order_item['user'] = '******'.format(
                    user['first_name'], user['last_name']) if user else None

                rating = self.order_repo.get_rating(user['id'], 'order',
                                                    order.id) if user else None

                order_item['user_rating'] = rating

                order_list.append(order_item)

        return self.handle_response('OK',
                                    payload={
                                        'orders': order_list,
                                        'meta': self.pagination_meta(orders)
                                    })

    def list_orders_date(self, start_date):
        """
		List all orders for a particular date
		:param start_date:
		:return:
		"""
        location_id = Auth.get_location()
        orders = self.order_repo.get_unpaginated(is_deleted=False,
                                                 date_booked_for=start_date,
                                                 location_id=location_id)

        order_list = []
        if len(orders) > 0:
            for order in orders:
                meal_items = self.order_repo.get(order.id).meal_item_orders
                try:
                    user = self.andela_service.get_user_by_email_or_id(
                        order.user_id)
                except Exception as e:
                    return str(e)

                order_item = order.serialize()
                order_item['mealItems'] = [
                    item.to_dict(
                        only=OrderController.default_meal_item_return_fields)
                    for item in meal_items
                ]
                order_item['user'] = '******'.format(
                    user['first_name'], user['last_name']) if user else None

                rating = self.order_repo.get_rating(user['id'], 'order',
                                                    order.id) if user else None
                order_item['user_rating'] = rating

                order_list.append(order_item)
        return self.handle_response('OK', payload={'orders': order_list})

    def list_orders_date_range(self, start_date, end_date):
        """
		List all orders for a particular date
		:param start_date:
		:param end_date:
		:return:
		"""
        location_id = Auth.get_location()
        orders = self.order_repo.get_range_paginated_options_all(
            start_date=start_date, end_date=end_date, location_id=location_id)

        order_list = []
        if len(orders.items) > 0:
            for order in orders.items:
                meal_items = self.order_repo.get(order.id).meal_item_orders
                try:
                    user = self.andela_service.get_user_by_email_or_id(
                        order.user_id)
                except Exception as e:
                    return str(e)
                order_item = order.serialize()
                order_item['mealItems'] = [
                    item.to_dict(
                        only=OrderController.default_meal_item_return_fields)
                    for item in meal_items
                ]
                order_item['user'] = '******'.format(
                    user['first_name'], user['last_name']) if user else None

                rating = self.order_repo.get_rating(user['id'], 'order',
                                                    order.id) if user else None

                order_item['user_rating'] = rating

                order_list.append(order_item)
        return self.handle_response('OK', payload={'orders': order_list})

    def get_order(self, order_id):
        """
		Gets all orders for an order_id
		:param order_id:
		:return:
		"""
        order = self.order_repo.get(order_id)
        if order:
            order_serialized = order.serialize()
            order_serialized['mealItems'] = [
                item.to_dict(
                    only=OrderController.default_meal_item_return_fields)
                for item in order.meal_item_orders
            ]

            try:
                user = self.andela_service.get_user_by_email_or_id(
                    order.user_id)
            except Exception as e:
                return str(e)
            order_serialized['user'] = '******'.format(
                user['first_name'], user['last_name']) if user else None

            rating = self.order_repo.get_rating(user['id'], 'order',
                                                order.id) if user else None

            order_serialized['user_rating'] = rating

            return self.handle_response('OK',
                                        payload={'order': order_serialized})
        return self.handle_response('Order not found', status_code=400)

    def get_order_by_user_id(self, user_id):
        """
		Gets all orders for a user by the user id
		:param user_id:
		:return: list of orders in json model
		"""
        orders = self.order_repo.filter_by(user_id=user_id, is_deleted=False)
        orders_list = []
        if len(orders.items) > 0:
            for order in orders.items:
                meal_items = self.order_repo.get(order.id).meal_item_orders
                try:
                    user = self.andela_service.get_user_by_email_or_id(
                        order.user_id)
                except Exception as e:
                    return str(e)
                order_item = order.serialize()
                order_item['mealItems'] = [
                    item.to_dict(
                        only=OrderController.default_meal_item_return_fields)
                    for item in meal_items
                ]
                order_item['user'] = '******'.format(
                    user['first_name'], user['last_name']) if user else None

                rating = self.order_repo.get_rating(user['id'], 'order',
                                                    order.id) if user else None

                order_item['user_rating'] = rating

                orders_list.append(order_item)
        return self.handle_response('OK', payload={'orders': orders_list})

    def get_order_by_user_id_date_range(self, user_id, start_date, end_date):
        """

		:param user_id:
		:param start_date:
		:param end_date:
		:return:
		"""
        orders = self.order_repo.get_range_paginated_options(
            user_id=user_id, start_date=start_date, end_date=end_date)
        order_list = []
        if len(orders.items) > 0:
            for order in orders.items:
                meal_items = self.order_repo.get(order.id).meal_item_orders
                user = self.andela_service.get_user_by_email_or_id(
                    order.user_id)
                order_item = order.serialize()
                order_item['mealItems'] = [
                    item.to_dict(
                        only=OrderController.default_meal_item_return_fields)
                    for item in meal_items
                ]
                order_item['user'] = '******'.format(
                    user['first_name'], user['last_name']) if user else None

                rating = self.order_repo.get_rating(user['id'], 'order',
                                                    order.id) if user else None

                order_item['user_rating'] = rating

                order_list.append(order_item)
        return self.handle_response('OK', payload={'orders': order_list})

    def create_order(self):
        """
		creates an order
		:return: order object
		"""
        user_id = Auth.user('id')
        location_id = Auth.get_location()
        date_booked_for, channel, meal_period, meal_items, menu_id = self.request_params(
            'dateBookedFor', 'channel', 'mealPeriod', 'mealItems', 'menuId')
        if self.order_repo.user_has_order(user_id, date_booked_for,
                                          meal_period):
            return self.handle_response(
                'You have already booked for this meal period.',
                status_code=400)

        location = LocationRepo().get(location_id)
        current_time = current_time_by_zone(location.zone)

        if datetime.strptime(date_booked_for, "%Y-%m-%d") < datetime.now():
            return self.handle_response(
                'You are not allowed to book for a date in the past',
                status_code=400)

        if int(current_time_by_zone(location.zone).strftime('%H')) > 15:
            if check_date_current_vs_date_for(
                    current_time, datetime.strptime(date_booked_for,
                                                    "%Y-%m-%d")):
                return self.handle_response(
                    'It is too late to book a meal for the selected date ',
                    status_code=400)

        meal_object_items = self.meal_item_repo.get_meal_items_by_ids(
            meal_items)

        new_order = self.order_repo.create_order(user_id, date_booked_for,
                                                 meal_object_items,
                                                 location_id, menu_id, channel,
                                                 meal_period).serialize()

        new_order['mealItems'] = [
            item.to_dict(only=OrderController.default_meal_item_return_fields)
            for item in meal_object_items
        ]
        return self.handle_response('OK',
                                    payload={'order': new_order},
                                    status_code=201)

    def update_order(self, order_id):
        """
		updates an order based on the order Id
		:param order_id:
		:return:
		"""
        date_booked_for, channel, meal_items, menu_id = self.request_params(
            'dateBookedFor', 'channel', 'mealItems', 'menuId')

        meal_object_items = []
        for meal_item_id in meal_items:
            meal_item = self.meal_item_repo.get(meal_item_id)
            meal_object_items.append(meal_item)

        order = self.order_repo.get(order_id)
        if order:
            if order.is_deleted:
                return self.handle_response('Order has already been deleted',
                                            status_code=400)
            updates = {}
            if date_booked_for:
                order_date_midnight = datetime.strptime(
                    date_booked_for, '%Y-%m-%d').replace(hour=00).replace(
                        minute=00).replace(second=00)
                current_time = datetime.now()
                if order_date_midnight - current_time < timedelta(
                        'hours' == 7):
                    return self.handle_response(
                        'It is too late to book meal for the selected date ',
                        status_code=400)
                updates['date_booked_for'] = datetime.strptime(
                    date_booked_for, '%Y-%m-%d')

            if menu_id:
                updates['menu_id'] = menu_id

            if channel:
                updates['channel'] = channel
            if meal_items:
                updates['meal_item_orders'] = meal_object_items

            updated_order = self.order_repo.update(order,
                                                   **updates).serialize()
            updated_order['mealItems'] = [
                item.to_dict(
                    only=OrderController.default_meal_item_return_fields)
                for item in order.meal_item_orders
            ]
            return self.handle_response('OK', payload={'order': updated_order})

        return self.handle_response('Invalid or incorrect order_id provided',
                                    status_code=400)

    def collect_order(self):
        """
		Collects order and mark as collected for a user Id
		:param user_id:
		:param order_type:
		:param order_date:
		:return:
		"""
        user_id, order_type, order_date = self.request_params(
            'userId', 'orderType', 'orderDate')

        order = self.order_repo.find_first(user_id=user_id,
                                           meal_period=order_type,
                                           date_booked_for=order_date,
                                           is_deleted=False)
        if not order:
            return self.handle_response(
                f'User has no {order_type} order for the date.',
                status_code=400)

        if order.order_status == OrderStatus.collected:
            return self.handle_response('Order already collected',
                                        status_code=400)

        updates = {}
        updates['order_status'] = OrderStatus.collected
        self.order_repo.update(order, **updates)

        return self.handle_response('Order successfully collected',
                                    payload={'order': order.serialize()})

    def check_order(self):
        """
		Checks if a user has an order for a particular date and period

		:return:
		"""
        user_id, order_type, order_date = self.request_params(
            'userId', 'orderType', 'orderDate')
        # get user_id from another method and reform to db's user id
        order = self.order_repo.find_first(user_id=user_id,
                                           meal_period=order_type,
                                           date_booked_for=order_date,
                                           is_deleted=False)
        if not order:
            return self.handle_response(
                f'User has no {order_type} order for this date')
        return self.handle_response('OK', payload={'order': order.serialize()})

    def delete_order(self, order_id):

        order = self.order_repo.get(order_id)

        if order:
            if order.is_deleted:
                return self.handle_response('Order has already been deleted',
                                            status_code=400)
            if Auth.user('id') != order.user_id:
                return self.handle_response(
                    'You cannot delete an order that is not yours',
                    status_code=403)

            updates = {}
            updates['is_deleted'] = True

            self.order_repo.update(order, **updates)
            return self.handle_response('Order deleted',
                                        payload={"status": "success"})
        return self.handle_response('Invalid or incorrect order_id provided',
                                    status_code=400)
class VendorRatingController(BaseController):
    def __init__(self, request):
        BaseController.__init__(self, request)
        self.vendor_rating_repo = VendorRatingRepo()
        self.vendor_repo = VendorRepo()
        self.menu_repo = MenuRepo()
        self.order_repo = OrderRepo()

    def list_ratings(self, vendor_id):
        '''retrieves a list of ratings for a specific vendor'''

        ratings = self.vendor_rating_repo.get_unpaginated(vendor_id=vendor_id)

        if ratings:
            ratings_list = [rating.serialize() for rating in ratings]
            return self.handle_response('OK',
                                        payload={'ratings': ratings_list})

        return self.handle_response('Expected vendor in request')

    def get_vendor_rating(self, rating_id):
        '''retrieves the details of a specific rating, giving the rating id'''
        rating = self.vendor_rating_repo.get(rating_id)
        if rating:
            rtng = rating.serialize()

            return self.handle_response('OK', payload={'rating': rtng})
        else:
            return self.handle_response('Bad Request', status_code=400)

    def create_vendor_rating(self):
        '''Adds a vendor rating during a specific engagement'''
        (vendor_id, comment, rating, channel,
         engagement_id) = self.request_params('vendorId', 'comment', 'rating',
                                              'channel', 'engagementId')
        user_id = Auth.user('id')

        if self.vendor_repo.get(vendor_id):

            rating = self.vendor_rating_repo.new_rating(
                vendor_id, user_id, rating, RatingType.engagement, 0,
                engagement_id, channel, comment)
            rtng = rating.serialize()

            return self.handle_response('Rating created',
                                        payload={'rating': rtng},
                                        status_code=201)

        return self.handle_response('Invalid vendor_id provided',
                                    status_code=400)

    def create_order_rating(self):
        '''Adds a order rating during a specific engagement '''

        (order_id, comment, rating,
         channel) = self.request_params('orderId', 'comment', 'rating',
                                        'channel')
        user_id = Auth.user('id')
        order = self.order_repo.get(order_id)
        if order:
            menu = self.menu_repo.get(order.menu_id)
            if menu:
                vendor_id = menu.vendor_engagement.vendor_id
                engagement_id = menu.vendor_engagement.id
                rating = self.vendor_rating_repo.new_rating(
                    vendor_id, user_id, rating, RatingType.order, order_id,
                    engagement_id, channel, comment)
                rating_obj = rating.serialize()
                if rating:
                    updates = {}
                    updates['has_rated'] = True
                    updated_order = self.order_repo.update(
                        order, **updates).serialize()
                return self.handle_response('Rating created',
                                            payload={'rating': rating_obj},
                                            status_code=201)

        return self.handle_response('Invalid vendor_id provided',
                                    status_code=400)

    def update_vendor_rating(self, rating_id):
        '''edits an existing rating'''

        rtng = self.vendor_rating_repo.get(rating_id)
        comment = self.get_json()['comment']

        if Auth.user(
                'id'
        ) == rtng.user_id:  #You cannot update someone else's rating
            if rtng:
                updates = {}
                if comment:
                    updates['comment'] = comment
                self.vendor_rating_repo.update(rtng, **updates)
                return self.handle_response(
                    'OK', payload={'rating': rtng.serialize()})
            return self.handle_response(
                'Invalid or incorrect rating_id provided', status_code=400)
        return self.handle_response(
            'You are not allowed to update a rating that is not yours',
            status_code=403)