import datetime
from typing import Sequence

from sqlalchemy import select, delete, func
from sqlalchemy.orm import selectinload, immediateload

from src.config.logger import shop_logger
from src.config.shops_config import load_shops_config
from src.data.model.db import partner_agent as db
from src.data.model.db import article as article_db
from src.data.model.schemas import order as schema
from src.data.model.schemas.shop import Shop
from src.data.repositories.base import BaseRepository

shop_config = load_shops_config()


class OrderRepository(BaseRepository):
    async def do_order(self, do_order: schema.DoOrder) -> schema.Order:
        # load all items from shopping cart
        shopping_cart_stmt = select(db.ShoppingCart).distinct().where(
            (db.ShoppingCart.partner_id == do_order.partner_id) &
            (db.ShoppingCart.agent_id == do_order.agent_id)
        )
        query = await self.async_session.execute(shopping_cart_stmt)
        shopping_cart_items = query.scalars().all()
        print(shopping_cart_items)
        id = await self.next_id(db.Order.id)
        order_items = list(map(self.__shopping_cart_item_to_order, shopping_cart_items))
        print(order_items)
        # create order from shopping cart
        order = db.Order(
            id=id,
            date=datetime.datetime.utcnow(),
            partner_id=do_order.partner_id,
            agent_id=do_order.agent_id if do_order.agent_id != -1 else None,
            processed=False,
            details=order_items
        )

        # save changes
        await self.async_session.merge(instance=order)

        # clear shopping cart of partner
        delete_stmt = delete(db.ShoppingCart).where(self.__where_shop_condition(do_order))
        await self.async_session.execute(delete_stmt)

        stmt = (select(db.Order, db.OrderDetail, db.ArticleDetail).
                select_from(db.Order).
                join(db.OrderDetail).
                join(db.ArticleDetail).
                options(selectinload(db.Order.details)).
                options(selectinload(db.OrderDetail.article_detail)).
                where(db.Order.id == id))

        order = await self.async_session.scalar(stmt)

        done_order = self.to_schema(order)
        await self.async_session.commit()
        shop_logger.info(f"Order created: {str(order)}")
        return done_order

    async def get_order(self, id: int):
        order = await self.async_session.get(db.Order, id, options=[
            immediateload(db.Order.details).immediateload(db.OrderDetail.article_detail)
        ])
        if not order:
            raise ValueError(f"There is not order with id {id}")
        return self.to_schema(order)

    async def get_orders(self, get_order: schema.GetOrder) -> schema.PagedOrders:
        stmt = (select(db.Order).distinct().
                offset(get_order.from_item).
                limit(get_order.take).
                join(db.OrderDetail).
                join(article_db.ArticleDetail).
                join(article_db.Article).
                options(selectinload(db.Order.details).selectinload(db.OrderDetail.article_detail), selectinload(db.Order.partner)))

        if get_order.shop == Shop.B2B:
            stmt.where((db.Order.partner_id == get_order.partner_id) & (db.OrderDetail.sales_order_type_code == 201))
        elif get_order.shop == Shop.PARTNERTOOL:
            current_mobi_season = shop_config.mobi_season
            if get_order.current_season:
                stmt = stmt.where(
                    (db.Order.agent_id == get_order.agent_id) &
                    (article_db.Article.season_nr == current_mobi_season) &
                    (db.OrderDetail.sales_order_type_code != 201)
                )
            else:
                stmt = stmt.where(
                    (db.Order.agent_id == get_order.agent_id) &
                    (article_db.Article.season_nr != current_mobi_season) &
                    (db.OrderDetail.sales_order_type_code != 201)
                )

        query = await self.async_session.scalars(stmt)
        orders = query.all()

        if get_order.shop == Shop.B2B:
            stmt = select(func.count()).select_from(db.Order).where(
                (db.Order.partner_id == get_order.partner_id) & (db.Order.agent_id == None))
        elif get_order.shop == Shop.PARTNERTOOL:
            stmt = select(func.count()).select_from(db.Order).where((db.Order.agent_id == get_order.agent_id))

        count = await self.async_session.scalar(stmt)
        result = schema.PagedOrders(
            page_count=len(orders),
            total_orders_count=count,
            orders=[
                self.to_schema(order)
                for order in orders
            ]
        )
        print(str(result))
        return result

    async def get_unprocessed_orders(self) -> Sequence[db.Order]:
        stmt = (select(db.Order).distinct().
                join(db.OrderDetail).
                join(db.ArticleDetail).
                join(article_db.Article).
                where(db.Order.processed == False).
                options(selectinload(db.Order.details).
                        selectinload(db.OrderDetail.article_detail).
                        selectinload(article_db.ArticleDetail.article),
                        selectinload(db.Order.details).
                        selectinload(db.OrderDetail.article_detail).
                        selectinload(article_db.ArticleDetail.color)))
        query = await self.async_session.scalars(stmt)
        orders = query.all()
        return orders

    def to_schema(self, order: db.Order) -> schema.Order:
        done_order_items = []
        for detail in order.details:
            done_order_items.append(
                schema.OrderItem(
                    article_number=detail.article_detail.article_number,
                    size=detail.article_detail.size,
                    color=detail.article_detail.color_code,
                    quantity=detail.quantity,
                    price=detail.price,
                    date_from=detail.delivery_date_from,
                    date_to=detail.delivery_date_to,
                    sales_order_type=detail.sales_order_type_code,
                    partner_delivery_number=detail.partner_delivery_number
                )
            )
        return schema.Order(
            id=order.id,
            partner=order.partner.name,
            partner_id=order.partner_id,
            date=order.date.strftime("%d.%m.%Y"),
            agent_id=order.agent_id,
            items=done_order_items,
            sum=sum(map(lambda i: i.price, done_order_items))
        )

    @staticmethod
    def __where_shop_condition(order_query: schema.DoOrder | schema.GetOrder):
        if order_query.shop == Shop.B2B:
            return db.Order.partner_id == order_query.partner_id
        else:
            return (db.Order.partner_id == order_query.partner_id) & (db.Order.agent_id == order_query.agent_id)

    @staticmethod
    def __shopping_cart_item_to_order(item: db.ShoppingCart):
        return db.OrderDetail(
            quantity=item.quantity,
            ean=item.ean,
            price=item.price,
            delivery_date_to=item.delivery_date_to,
            delivery_date_from=item.delivery_date_from,
            sales_order_type_code=item.sales_order_type_code
        )
