from datetime import datetime
from typing import (
    List,
    Dict,
    Tuple,
    Any,
)

from msgbus import MBEasyContext
from persistence import Connection
from old_helper import BaseRepository

from dailygoals_services.model import (
    Order,
    OrderItem,
    GetPaidOrdersException,
    GetOrderItemsException,
)
from dailygoals_services.model.repository import OrderRepository as OrderServicesRepository


class OrderRepository(
    OrderServicesRepository,    # type: OrderServicesRepository
    BaseRepository,             # type: BaseRepository
):

    def __init__(
        self,
        mb_context,     # type: MBEasyContext
        pos_list,       # type: List
    ):
        # type: (...) -> None

        super(OrderRepository, self).__init__(mb_context)
        self.pos_list = pos_list

    def get_paid_ticket(
        self,
        business_period,    # type: str
    ):
        # type: (...) -> List

        def inner_func(
            conn,   # type: Connection
        ):
            # type: (...) -> List[Tuple[Any, Any]]

            query = self._PaidTicketsQuery.format(business_period)
            users_average_ticket = [(x.get_entry(0), x.get_entry(1)) for x in conn.select(sql=query)]
            return users_average_ticket

        return self.execute_parallel_in_all_databases_returning_flat_list(
            method=inner_func,
            pos_list=self.pos_list,
        )

    def get_paid_orders_ticket(
        self,
        business_period,    # type: str
    ):
        # type: (...) -> List

        def inner_func(
            conn,   # type: Connection
        ):
            # type: (...) -> List

            query = self._PaidOrdersTicketQuery.format(business_period)
            orders = [[(x.get_entry(0), x.get_entry(1)) for x in conn.select(sql=query)][0]]
            return orders

        return self.execute_parallel_in_all_databases_returning_flat_list(
            method=inner_func,
            pos_list=self.pos_list,
        )

    def get_paid_orders(
        self,
        operator_id,    # type: str
        initial_date,   # type: datetime
        end_date,       # type: datetime
    ):
        # type: (...) -> List

        try:
            def inner_func(
                conn,   # type: Connection
            ):
                # type: (...) -> List

                query = self._PaidOrdersQuery
                date_format = "%Y%m%d"

                query = query.format(
                    initial_date.strftime(date_format),
                    end_date.strftime(date_format),
                    int(operator_id)
                )
                if operator_id in ["", "0", "None"]:
                    query = query.replace("user={},".format(int(operator_id)), "user=")

                all_orders = {}  # type: Dict[int, Order]
                order_with_payments = [(
                    int(x.get_entry(0)),
                    int(x.get_entry(1)),
                    datetime.strptime(x.get_entry(2), "%Y%m%d"),
                    float(x.get_entry(3)),
                    int(x.get_entry(4)),
                    float(x.get_entry(5))
                ) for x in conn.select(sql=query)]

                for order_tuple in order_with_payments:
                    order_id = order_tuple[0]
                    order_state = order_tuple[1]
                    order_date = order_tuple[2]

                    if order_id not in all_orders:
                        order = Order(
                            order_id=order_id,
                            date=order_date,
                            state=order_state,
                        )
                        all_orders[order_id] = order

                return list(all_orders.values())

            return self.execute_parallel_in_all_databases_returning_flat_list(
                method=inner_func,
                pos_list=self.pos_list,
            )
        except Exception as _:
            raise GetPaidOrdersException()

    def get_orders_items(
        self,
        paid_orders,    # type: List[OrderItem]
    ):
        # type: (...) -> List[OrderItem]

        try:
            def inner_func(
                conn,   # type: Connection
            ):
                # type: (...) -> List[OrderItem]

                query = self._OrderItemsQuery
                order_list = map(lambda order: order.order_id, sorted(paid_orders))
                order_list = ','.join(map(str, order_list))
                query = query.format(order_list)

                all_order_items = {}    # type: Dict[str, OrderItem]
                order_items = [(
                    int(x.get_entry(0)),
                    float(x.get_entry(1)),
                    str(x.get_entry(2)),
                    str(x.get_entry(3)),
                    str(x.get_entry(4)),
                    str(x.get_entry(5)),
                    str(x.get_entry(6))
                ) for x in conn.select(query)]

                for order_tuple in order_items:
                    prod_code = order_tuple[0]
                    quantity = order_tuple[1]
                    item_id = order_tuple[2]
                    price = order_tuple[3] if order_tuple[3] != 'None' else order_tuple[6]

                    if quantity == 0 and price == 'None':
                        continue

                    split_item_id = item_id.split('.')
                    if len(split_item_id) >= 2:
                        has_context = ".".join(split_item_id[1:]) if len(item_id) > 1 else False
                        father_context = split_item_id[1]
                    else:
                        has_context = False
                        father_context = ''

                    if father_context:
                        quantity = self._group_item_qty(
                            context=father_context,
                            order_tuple=order_tuple,
                            order_items=order_items,
                            quantity=quantity,
                        )

                    key = (str(prod_code) + '.' + has_context) if has_context else str(prod_code)
                    no_context_key = has_context and key not in all_order_items.keys()
                    no_prod_code_key = not has_context and (str(prod_code) not in all_order_items.keys())

                    if no_context_key or no_prod_code_key:
                        order_item = OrderItem(
                            name=u"",
                            quantity=quantity,
                            pcode=prod_code,
                            unit_price=price,
                            context=item_id,
                        )
                        all_order_items[key] = order_item
                    else:
                        all_order_items[key].quantity += quantity

                return list(all_order_items.values())

            return self.execute_parallel_in_all_databases_returning_flat_list(
                method=inner_func,
                pos_list=self.pos_list,
            )
        except Exception as _:
            raise GetOrderItemsException()

    @staticmethod
    def _group_item_qty(
        context,        # type: str
        order_tuple,    # type: tuple
        order_items,    # type: List
        quantity,       # type: str
    ):
        # type: (...) -> float

        order_id = order_tuple[4]
        line_number = order_tuple[5]
        father_item = filter(
            lambda item: (
                item[0] == int(context)
                and item[4] == order_id
                and line_number == item[5]
            ),
            order_items
        )
        updated_quantity = float(quantity) * father_item[0][1]
        return updated_quantity

    _PaidTicketsQuery = """
        SELECT UserId, SUM(NumberOfSeats) AS seats
        FROM TableService tb
        WHERE BusinessPeriod = '{}' 
          AND (OrderId IS NOT NULL OR TotalAmount IS NOT NULL)
        GROUP BY UserId
    """
    _PaidOrdersQuery = """
        SELECT o.OrderId, o.StateId, o.BusinessPeriod, o.TotalGross, ot.TenderId, 
            ot.TenderAmount - COALESCE(ot.ChangeAmount, 0)
        FROM Orders o
        INNER JOIN OrderTender ot
        ON o.OrderId = ot.OrderId
        WHERE o.BusinessPeriod >= '{0}' 
          AND o.BusinessPeriod <= '{1}' 
          AND o.StateId = 5 
          AND o.SessionId LIKE '%user={2},%'
    """

    _PaidOrdersTicketQuery = """\
        SELECT SessionId, COUNT(*) AS orders
        FROM orders
        WHERE StateId = 5 AND BusinessPeriod = '{}'
    """

    _OrderItemsQuery = """
        SELECT * FROM (
            SELECT PartCode, SUM(OrderedQty) AS OrderedQtySum, ItemId, DefaultUnitPrice, 
                OrderId, LineNumber, AddedUnitPrice
                FROM (
                    SELECT 
                        t.PartCode,
                        t.OrderedQty AS OrderedQty,
                        t.ItemId,
                        t.LineNumber,
                        t.OrderId,
                        p.DefaultUnitPrice,
                        p.AddedUnitPrice
                    FROM OrderItem t
                    JOIN OrderItem u ON t.OrderId = u.OrderId AND t.LineNumber = u.LineNumber
                    JOIN Orders s ON t.OrderId = s.OrderId
                    JOIN ProductKernelParams pkp ON pkp.ProductCode = t.PartCode
                    LEFT JOIN OrderPrice p 
                        ON (t.PriceKey = p.PriceKey AND t.LastOrderedQty IS NULL)
                        AND p.OrderId = t.OrderId
                    WHERE s.StateId = 5
                      AND u.Level = '0' 
                      AND t.OrderId IN ({0})
                      AND pkp.ProductType <> 1
                      AND COALESCE(t.OrderedQty, t.DefaultQty) > 0
                ) T
            GROUP BY OrderId, ItemId, PartCode, LineNumber
        ) 
        WHERE OrderedQtySum is not null
    """
