from copy import deepcopy
from collections import defaultdict
from datetime import datetime, timedelta, UTC
from logging import warning, info, error
from zoneinfo import ZoneInfo
from .models import OrderItem, Variation, Order
from .serializers import TopListItemSerializer, TopListSerializer

from gmz.base import withplenty
from gmz.lib.plentyrest import PlentyOrder

ORDERFILTER_ALL = {
    "conditionType": "and",
    "fields": [
        {"field": "statusId", "operator": "gte", "value": 3},
        {"field": "orderTypeId", "operator": "eq", "value": 1},
    ],
    "groups": [],
    "with": [],
    "lazyLoaded": False,
}

FBAORIGINS = [
    104.00,
    104.01,
    104.02,
    104.03,
    104.04,
    104.05,
    104.06,
    104.07,
    104.08,
    104.09,
    104.10,
    104.11,
    104.12,
    104.13,
    104.14,
    104.15,
    104.16,
    104.17,
    104.18,
    104.19,
    104.20,
    104.21,
    104.22,
    104.23,
    104.24,
    104.25,
    104.26,
]


@withplenty
def fetch_new_orders(plenty):
    try:
        last = Order.objects.latest()
    except Order.DoesNotExist:
        last = None
    if last:
        last = last.date.astimezone(ZoneInfo("Europe/Berlin"))
    else:
        last = datetime.now(ZoneInfo("Europe/Berlin")) - timedelta(days=3)
    filterparams = deepcopy(ORDERFILTER_ALL)
    filterparams["fields"].extend(
        [
            {
                "field": "updatedAt",
                "operator": "gt",
                "value": last.replace(microsecond=0).isoformat(),
            },
        ]
    )
    orders = PlentyOrder.get_by_complex_search(plenty, filterparams)
    for order in orders:
        amounts = order["amounts"]
        if len(amounts) > 1:
            info(f"More than one amount in order {order['id']}")
        dborder, _ = Order.objects.update_or_create(
            orderid=order["id"],
            defaults={
                "date": datetime.fromisoformat(order["createdAt"]),
                "status": order["statusId"],
                "totalnet": amounts[0]["netTotal"],
                "totalgross": amounts[0]["grossTotal"],
                "shippingprofile": order.get_shipping_profile(),
                "origin": order["referrerId"],
            },
        )
        present = set()
        for oi in (oi for oi in order.orderitems if oi["typeId"] in (1, 2)):
            present.add(oi["id"])
            dbvariation, _ = Variation.objects.get_or_create(
                vid=oi["itemVariationId"],
                defaults={"vid": oi["itemVariationId"], "name": oi["orderItemName"]},
            )
            OrderItem.objects.update_or_create(
                orderitemid=oi["id"],
                defaults={
                    "variation": dbvariation,
                    "quantity": oi["quantity"],
                    "order": dborder,
                },
            )
        # remove orderitems not present anymore
        OrderItem.objects.filter(order_id=order["id"]).exclude(
            orderitemid__in=present
        ).delete()


class TopListItem:
    def __init__(self, vid, name, quantity, orderquantity, trend=-2):
        self.vid = vid
        self.name = name
        self.quantity = quantity
        self.orderreferencecount = orderquantity
        self.trend = trend


class TopList:
    def __init__(self, startdate, enddate, totalgross=0, ordercount=0, items=None):
        self.startdate = startdate
        self.enddate = enddate
        self.ordercount = ordercount
        self.totalgross = totalgross
        self.items = list(items) if items else []

    def as_json(self):
        items = [TopListItemSerializer(i).data for i in self.items]
        return TopListSerializer(
            {
                "startdate": self.startdate,
                "enddate": self.enddate,
                "ordercount": self.ordercount,
                "totalgross": self.totalgross,
                "items": items,
            }
        ).data

    def add_trends_compared_to(self, prev):
        current = {x.vid: x for x in self.items}
        prev = {x.vid: x for x in prev.items}
        diffquantities = []
        for vid, item in current.items():
            previtem = prev.get(vid, None)
            if previtem:
                diffquantities.append(item.quantity - previtem.quantity)
        if not diffquantities:
            return
        maxdiff = abs(max(diffquantities))
        mindiff = abs(min(diffquantities))
        for vid, item in current.items():
            previtem = prev.get(vid, None)
            if not previtem:
                item.trend = 1
                continue
            else:
                diff = item.quantity - previtem.quantity
            if diff > 0:
                item.trend = min(diff / maxdiff, 1)
            elif diff < 0:
                item.trend = diff / mindiff
            else:
                item.trend = 0

    @classmethod
    def generate_toplist(cls, startdate, enddate, orders):
        itemdict = {}
        totalgross = 0
        orderreferences = defaultdict(set)
        for order in orders:
            for orderitem in order.orderitem_set.all():
                item = itemdict.get(orderitem.variation.vid, None)
                # filter out dummy item
                if orderitem.variation.vid == 1334:
                    continue
                if item is None:
                    item = itemdict[orderitem.variation.vid] = TopListItem(
                        orderitem.variation.vid,
                        orderitem.variation.name,
                        orderitem.quantity,
                        1,
                    )
                else:
                    item.quantity += orderitem.quantity
                orderset = orderreferences[orderitem.variation.vid]
                orderset.add(order.orderid)
                item.orderreferencecount = len(orderset)
            totalgross += order.totalgross
        return cls(startdate, enddate, totalgross, orders.count(), itemdict.values())

    @classmethod
    def from_db(cls, startdate=None, enddate=None, subset=None):
        prevorders = None
        match (startdate, enddate):
            case (None, None):
                orders = Order.objects.filter()
            case (None, enddate):
                enddate = enddate.astimezone(UTC)
                orders = Order.objects.filter(date__lte=enddate)
            case (startdate, None):
                startdate = startdate.astimezone(UTC)
                orders = Order.objects.filter(rdate__gte=startdate)
            case (startdate, enddate):
                startdate = startdate.astimezone(UTC)
                enddate = enddate.astimezone(UTC)
                orders = Order.objects.filter(date__range=(startdate, enddate))
                delta = enddate - startdate
                prevstartdate = startdate - delta
                prevenddate = enddate - delta
                prevorders = Order.objects.filter(
                    date__range=(prevstartdate, prevenddate)
                )
        # remove some dropshipping referrers
        orders = orders.filter(status__gte=3, status__lte=7).exclude(
            origin__in=[26.0, 28.0]
        )
        # do additional filtering on the orders
        match subset:
            case "fba":
                orders = orders.filter(origin__in=FBAORIGINS)
            case "nofba":
                orders = orders.exclude(origin__in=FBAORIGINS)
            case _:
                # no subset specified
                pass
        toplist = cls.generate_toplist(startdate, enddate, orders)
        if prevorders:
            prevorders = prevorders.filter(status__gte=3, status__lte=7)
            prevtoplist = cls.generate_toplist(prevstartdate, prevenddate, prevorders)
            toplist.add_trends_compared_to(prevtoplist)
        return toplist

    @classmethod
    def from_db_unshipped(cls):
        orders = (
            Order.objects.filter(status__gte=4, status__lt=7)
            .exclude(status__in=[4.1, 4.010, 5.069, 5.070, 5.071])
            .exclude(origin__in=[26.0, 28.0])
        )
        return cls.generate_toplist(None, None, orders)
