import csv
import locale
import re
import time
import urllib

from collections import defaultdict
from logging import info, warning, error

from ..base import *
from ..config import *

# used to convert numbers to german fraction seperator ','
locale.setlocale(locale.LC_NUMERIC, 'de_DE.UTF-8')


def tocomma(val):
    if val == '':
        return val
    v = float(val)
    return locale.format_string("%.2f", v)


class RepricingCSVRow(dict):

    @property
    def sellerlogicactive(self):
        return self['SellerlogicTag'] == '17'

    @property
    def repricingactive(self):
        return self['PreisoptimierungTag'] == '118'


class ShopSPYRow(dict):

    @staticmethod
    def calculate_min_max_for_margin(purchase, marginpercentage, marginmin,
                                     shipping=3.90, shippingfreelimit=29,
                                     maxmarginpercentage=200,
                                     solomarginpercentage=200):
        """Caclulate min/max price based on percentage margin on pruchase
        price.

        purchase: net purchase price
        marginpercentage: minimum margin in percentage on pruchase price
        marginmin: minimum absolute margin
        shipping: gross DE shipping costs
        shippingfreelimit: inclusive gross lower limit for free DE shipping
        maxmarginpercentage: maximum margin in percentage of purchase price
        solomarginpercentage: solo margin in percentage of purchase price

        returns:
        (minprice, maxprice , soloprice)
        """

        def ninetynine(price):
            # nearest .99 price
            above = int(price) + 0.99
            below = int(price) - 0.01
            if above - price < 0.50:
                price = above
            else:
                price = below
            return price

        def calculate_gross_salesprice(margin):
            gross = ninetynine((purchase + margin) * 1.19)
            if gross >= shippingfreelimit:
                gross = ninetynine(gross + shipping)
            return gross

        margin = (purchase / 100) * marginpercentage
        minprice = calculate_gross_salesprice(max(margin, marginmin))
        maxprice = calculate_gross_salesprice(
            (purchase / 100) * maxmarginpercentage)
        soloprice = calculate_gross_salesprice(
            (purchase / 100) * solomarginpercentage)
        return (minprice, maxprice, soloprice)

    @classmethod
    def from_repricingrow(cls, inrow):
        outrow = cls()
        outrow['SKU'] = inrow['VariationId']
        outrow['GTIN*'] = inrow['GTIN']
        outrow['Titel*'] = inrow['ItemDescriptionName']
        outrow['Marke*'] = inrow['ItemManufacturerName']
        if inrow[cls.OPTIMIZATIONPROPERTY]:
            purchase = float(inrow['PurchasePrice'])
            movingaverage = float(inrow['MovingAveragePrice'])
            purchaseprice = max(purchase, movingaverage)
            strategy = inrow[cls.OPTIMIZATIONPROPERTY][:4]
            if strategy == '[00]' or purchaseprice <= 0:
                outrow['Preisoptimierung aktiv*'] = '0'
                outrow['Min.-Preis*'] = ''
                outrow['Max.-Preis*'] = ''
                outrow['Solo-Preis*'] = ''
            else:
                func, params = cls.STRATEGYMAP[strategy]
                profiles = set(inrow['Versandprofile'].split(','))
                if '56' in profiles:
                    # Spedition cost default
                    kwargs = {'shipping': 60}
                elif '11' in profiles:
                    # GLS
                    kwargs = {'shipping': 10}
                else:
                    kwargs = {}
                minprice, maxprice, soloprice = func(purchaseprice, *params,
                                                     **kwargs)
                outrow['Preisoptimierung aktiv*'] = '1'
                if inrow[cls.FIXEDMINPRICEKEY]:
                    minprice = float(inrow[cls.FIXEDMINPRICEKEY])
                if inrow[cls.FIXEDMAXPRICEKEY]:
                    maxprice = float(inrow[cls.FIXEDMAXPRICEKEY])
                    soloprice = min(soloprice, maxprice)
                elif inrow['PoolpiratStreichpreis']:
                    maxprice = float(inrow['PoolpiratStreichpreis'])
                    soloprice = min(soloprice, maxprice)
                elif inrow['UVP']:
                    maxprice = float(inrow['UVP'])
                    soloprice = min(soloprice, maxprice)
                outrow['Min.-Preis*'] = tocomma(minprice)
                outrow['Max.-Preis*'] = tocomma(maxprice)
                outrow['Solo-Preis*'] = tocomma(soloprice)
        else:
            # ignore if no optimization property set
            return None
        m = re.search(r'Strategie[ ]?\(([0-9])\)',
                      inrow[cls.OPTIMIZATIONPROPERTY])
        if m:
            outrow['Strategie*'] = m.groups()[0]
        else:
            outrow['Strategie*'] = ''
        return outrow


class GomazingShopSPYRow(ShopSPYRow):
    OPTIMIZATIONPROPERTY = 'PreisoptimierungShopwareGomazing'
    FIXEDMINPRICEKEY = 'GomazingPriceMin'
    FIXEDMAXPRICEKEY = 'GomazingPriceMax'

    STRATEGYMAP = {
        '[01]': (ShopSPYRow.calculate_min_max_for_margin, (20, 1)),
        '[02]': (ShopSPYRow.calculate_min_max_for_margin, (20, 1))
    }


class PoolpiratShopSPYRow(ShopSPYRow):
    OPTIMIZATIONPROPERTY = 'PreisoptimierungShopifyPoolpirat'
    FIXEDMINPRICEKEY = 'PoolpiratPriceMin'
    FIXEDMAXPRICEKEY = 'PoolpiratPriceMax'

    STRATEGYMAP = {
        '[01]': (ShopSPYRow.calculate_min_max_for_margin, (10, 1)),
        '[02]': (ShopSPYRow.calculate_min_max_for_margin, (10, 1))
    }


CHANNEL2ROWCLASSMAP = {
    'gomazing': GomazingShopSPYRow,
    'poolpirat': PoolpiratShopSPYRow
}


class AmazonRepricerRow(dict):

    # minimum columns required
    # SKU, Min.-Preis, Max.-Preis, Solo-Preis, Strategie, Preisoptimierung
    # aktiv

    OPTIMIZATIONPROPERTY = 'PreisoptimierungAmazon'
    MINPRICEKEY = 'AmazonPriceMin'
    MAXPRICEKEY = 'AmazonPriceMax'
    SOLOPRICEKEY = 'AmazonPriceSolo'

    @classmethod
    def from_repricingrow(cls, inrow):
        """outputs a list of rows"""

        outrows = []
        outrow = cls()
        if not inrow['AmazonSku']:
            return []
        outrow['SKU'] = inrow['AmazonSku']
        outrow['EAN*'] = inrow['GTIN']
        outrow['Min.-Preis*'] = tocomma(inrow[cls.MINPRICEKEY])
        outrow['Max.-Preis*'] = tocomma(inrow[cls.MAXPRICEKEY])
        outrow['Solo-Preis*'] = tocomma(inrow[cls.SOLOPRICEKEY])
        strategy = inrow[cls.OPTIMIZATIONPROPERTY][:4]
        outrow['Preisoptimierung aktiv*'] = '1'
        if strategy == '[00]':
            outrow['Preisoptimierung aktiv*'] = '0'
        m = re.search(r'Strategie[ ]?\(([0-9]+)\)',
                      inrow[cls.OPTIMIZATIONPROPERTY])
        if m:
            outrow['Strategie*'] = m.groups()[0]
        else:
            outrow['Strategie*'] = ''
        outrows.append(outrow)
        # very similar output for additional skus
        additional = [a for a in inrow['AdditionalSkus'].split(',') if
                      a.startswith('4.00;')]
        for asku in additional:
            extra = outrow.copy()
            extra['SKU'] = asku.rsplit(';', maxsplit=1)[1]
            outrows.append(extra)
        return outrows


class RepricingCSV:

    def __init__(self, csvurl):
        cachefile = PLENTYREPRICINGCACHEFILE
        if (cachefile.exists() and time.time() - cachefile.stat().st_mtime <
                PLENTYPRICINGCACHESECONDS):
            f = open(cachefile, newline='')
            data = f.readlines()
        else:
            f = urllib.request.urlopen(csvurl)
            data = f.read().decode('utf-8').split('\n')
            with open(PLENTYREPRICINGCACHEFILE, 'w') as cf:
                cf.write('\n'.join(data))
        f.close()
        self.rows = []
        reader = csv.DictReader(data, delimiter=';')
        for row in reader:
            self.rows.append(RepricingCSVRow(row))

    def as_skutorow_map(self):
        return dict(((r['VariationId'], r) for r in self.rows))

    def to_snaptrade_shopspy_feed(self, channel, outpath):
        """Generates item feed with min/max prices for ShopSPY"""

        rowclass = CHANNEL2ROWCLASSMAP[channel]
        outrows = []
        for row in (r for r in self.rows if r.repricingactive):
            r = rowclass.from_repricingrow(row)
            if r:
                outrows.append(r)
        if outrows:
            with open(outpath, 'w') as f:
                writer = csv.DictWriter(f, outrows[0].keys(), delimiter=';')
                writer.writeheader()
                writer.writerows(outrows)

    def to_snaptrade_amazon_feed(self, outpath):
        """Generates item feed with min/max prices for Amazon repricer"""
        outrows = []
        for row in (r for r in self.rows):
            r = AmazonRepricerRow.from_repricingrow(row)
            if r:
                outrows.extend(r)
        if outrows:
            with open(outpath, 'w') as f:
                writer = csv.DictWriter(f, outrows[0].keys(), delimiter=';')
                writer.writeheader()
                writer.writerows(outrows)


class ShopSpyCSV:

    def __init__(self, csvpath):
        self.rows = []
        with open(csvpath, newline='') as f:
            reader = csv.DictReader(f, delimiter=';')
            for row in reader:
                self.rows.append(row)

    def update_poolpirat_price(self, reprow, row, plenty=None):
        rprice = locale.atof(row['R-Preis'])
        if not reprow['PoolpiratPrice']:
            # in case no price was set yet
            gprice = 9999.99
        else:
            gprice = float(reprow['PoolpiratPrice'])
        if rprice != gprice:
            info(f"Setting {row['SKU']} to {rprice:02f}")
            plenty.post_item_variation_price(
                reprow['ItemId'], row['SKU'], 33, rprice)
            # mark variation as changed or the shopify plugin will not update
            # it
            plenty.touch_item_variation(reprow['ItemId'], row['SKU'])

    def update_gomazing_price(self, reprow, row, plenty=None):
        print(row)
        rprice = locale.atof(row['R-Preis'])
        if not reprow['GomazingPrice']:
            # in case no price was set yet
            gprice = 9999.99
        else:
            gprice = float(reprow['GomazingPrice'])
            if rprice != gprice:
                info(f"Setting {row['SKU']} to {rprice:02f}")
                plenty.post_item_variation_price(
                    reprow['ItemId'], row['SKU'], 51, rprice)

    @withplenty
    def update_prices(self, rcsv, plenty=None):
        """Use repricing prices from CSV but only if active and changed

        rcsv: RepricingCSV
        """

        skutorow = rcsv.as_skutorow_map()
        for r in self.rows:
            if r['Preisoptimierung aktiv*'] == '0':
                continue
            if not r['R-Preis']:
                continue
            reprow = skutorow.get(r['SKU'], None)
            if (reprow and reprow.repricingactive):
                if (reprow['PreisoptimierungShopifyPoolpirat'][:4]
                        not in ('', '[00]')):
                    self.update_poolpirat_price(reprow, r, plenty)
                if (reprow['PreisoptimierungShopwareGomazing'][:4]
                        not in ('', '[00]')):
                    self.update_gomazing_price(reprow, r, plenty)


def cmd_shopfeed(args):
    rcsv = RepricingCSV(PLENTYREPRICINGURL)
    rcsv.to_snaptrade_shopspy_feed(args.channel, args.feedpath)


def cmd_amazonfeed(args):
    rcsv = RepricingCSV(PLENTYREPRICINGURL)
    rcsv.to_snaptrade_amazon_feed(args.feedpath)


def cmd_reprice(args):
    rcsv = RepricingCSV(PLENTYREPRICINGURL)
    for csvpath in args.csvpaths:
        spycsv = ShopSpyCSV(csvpath)
        spycsv.update_prices(rcsv)
