from itertools import islice
from logging import info, warning, error

from ..base import *
from ..config import *
from ..lib.plentyrest import PlentyVariation, PlentyItem, PlentyMarketListing,\
    PlentyData


# maps ebay source profile to target profile depending on handling days
# the key is the minimum number of days, the value is a dict mapping target to
# source profiles

PROFILE_PARCEL_LESS_THAN_2KG = set([17, 146, 139, 142, 147, 179])

EBAYPROFILEMAP = {
    1: {
        17: PROFILE_PARCEL_LESS_THAN_2KG
    },
    3: {
        139: PROFILE_PARCEL_LESS_THAN_2KG
    },
    7: {
        179: PROFILE_PARCEL_LESS_THAN_2KG
    },
    15: {
        146: PROFILE_PARCEL_LESS_THAN_2KG
    },
    30: {
        142: PROFILE_PARCEL_LESS_THAN_2KG
    },
    40: {
        147: PROFILE_PARCEL_LESS_THAN_2KG
    },
}

EBAYPREORDERTEXT = """
<p><strong><span style="color:#ff0000;"><span style="font-size:24px;">
VORVERKAUF! Lieferung ab {0}!</span></span></strong></p>
"""

# maps availability symbols min days to IDs, keep it sorted by days
AVAILABILITYMAP = {
    1: 1,
    6: 2,
    15: 3,
    30: 4,
    45: 5,
    60: 6,
    90: 7
}


# import from itertools on version upgrade to 3.12
def batched(iterable, n):
    if n < 1:
        raise ValueError('n must be at least one')
    iterator = iter(iterable)
    while batch := tuple(islice(iterator, n)):
        yield batch


class ItemListingPair:
    """Holds an item and its ebay listing to facilitate bidirection updates"""

    def __init__(self, item, marketlisting, plenty):
        """
        item - PlentyItem
        marketlisting - PlentyMarketListing
        """

        self.item = item
        self.marketlisting = marketlisting
        self.plenty = plenty

    def update_profile(self):
        days_left = self.item.days_till_release
        if days_left is None:
            warning(f"item {self.item.id} has no release date on "
                    f"its variations")
            return
        oldprofile = int(self.marketlisting.shippingprofile)
        for days in sorted(EBAYPROFILEMAP.keys()):
            newprofile = None
            if days > days_left:
                for target, sources in EBAYPROFILEMAP[days].items():
                    if oldprofile in sources:
                        newprofile = target
                        break
                if newprofile:
                    break

        if newprofile is None:
            warning(f"could not find new shipping profile for MLID "
                    f"{self.mlid} with {days_left} days left "
                    f"until release")
            return
        if newprofile == oldprofile:
            return
        info(f"setting shipping profile for MLID {self.mlid} "
             f"from {oldprofile} to {newprofile}")
        self.plenty.put_listings_markets_shippingprofile(
            self.marketlisting['id'], 2.08, newprofile)

    def update_text(self):
        rdate = self.item.latest_release_date
        if not rdate:
            return
        if self.item.days_till_release > 1:
            self.plenty.put_listings_markets_texts(
                self.marketlisting['id'],
                EBAYPREORDERTEXT.format(rdate.strftime("%d.%m.%Y")))
        else:
            self.plenty.put_listings_markets_texts(
                self.marketlisting['id'], '')

    @property
    def mlid(self):
        return self.marketlisting['id']


@withplenty
def update_ebay_listings(mlids, plenty=None):
    historyids = []
    for chunk in batched(mlids, 50):
        joinedmlids = ','.join((str(c) for c in chunk))
        r = plenty.get_listings_markets_histories(listingMarketId=joinedmlids,
                                                  referrerId=2.08)
        historyids.extend([h['id'] for h in r])
    if not historyids:
        return
    r = plenty.put_listings_markets_histories_updates(
        historyids, ['shipping', 'description']
    )
    if not r.json()['createdJobIds']:
        error(f"no update jobs created for MLIDs {joinedmlids}")


@withplenty
def update_variation_availability(item, plenty=None):
    for v in item.variations:
        days_left = v.days_till_release
        if days_left is None:
            warning(f"variation {v.id} has no release date")
            continue
        availabilitymap = sorted(AVAILABILITYMAP.items(), key=lambda x: x[0])
        for days, availability in availabilitymap:
            if days > days_left:
                if v.availability != availability:
                    if availability == 1 and v.stock_for_warehouse(133) <= 0:
                        warning(f"could switch availability to 1 for "
                                f"variation {v.id} but no physical stock yet")
                        break
                    data = PlentyData({'availability': availability})
                    info(f"setting variation {v.id} availability from "
                         f"{v.availability} to {availability}")
                    plenty.put_item_variation(item.id, v.id, data)
                break


@withplenty
def cmd_profilesync(args, plenty=None):
    items = PlentyItem.get_all_with_variation_tag(plenty, 8, ['stock'])
    changed_mlids = []
    for item in items:
        if (item.days_till_release is not None and
                item.days_till_release <= 1 and not
                item.all_variations_have_stock_in_warehouse(133)):
            warning(f"item {item.id} should be available but no physical "
                    f"stock booked in warehouse 133 yet. Ignoring.")
            continue
        else:
            update_variation_availability(item)
        listing = PlentyMarketListing.get_for_item(plenty, item.id)
        if not listing:
            warning(f"item {item.id} does not have a listing. Ignoring")
            continue
        history = plenty.get_listings_markets_histories(itemId=item.id,
                                                        statusId=1,
                                                        referrerId=2.08)
        if not history:
            warning(f"item {item.id} does not have an active listing. "
                    f"Ignoring")
            continue
        ilp = ItemListingPair(item, listing, plenty)
        ilp.update_profile()
        ilp.update_text()
        changed_mlids.append(ilp.mlid)
    update_ebay_listings(changed_mlids)
