# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from decimal import Decimal

import pyactiveresource
import shopify

from trytond.i18n import gettext
from trytond.model import ModelSQL, ModelView, fields
from trytond.model.exceptions import AccessError
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, If
from trytond.tools import grouped_slice, slugify
from trytond.transaction import Transaction

from .common import IdentifiersMixin, IdentifiersUpdateMixin


class Category(IdentifiersMixin, metaclass=PoolMeta):
    __name__ = 'product.category'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.add('name')

    def get_shopify(self, shop):
        shopify_id = self.get_shopify_identifier(shop)
        custom_collection = None
        if shopify_id:
            try:
                custom_collection = shopify.CustomCollection.find(shopify_id)
            except pyactiveresource.connection.ResourceNotFound:
                pass
        if custom_collection is None:
            custom_collection = shopify.CustomCollection()
        custom_collection.title = self.name[:255]
        custom_collection.published = False
        return custom_collection

    def get_shopify_metafields(self, shop):
        return {}


class TemplateCategory(IdentifiersUpdateMixin, metaclass=PoolMeta):
    __name__ = 'product.template-product.category'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.update(['template', 'category'])

    @classmethod
    def create(cls, vlist):
        records = super().create(vlist)
        cls.set_shopify_to_update(records)
        return records

    @classmethod
    def delete(cls, records):
        cls.set_shopify_to_update(records)
        super().delete(records)

    @classmethod
    def get_shopify_identifier_to_update(cls, records):
        return sum((list(r.template.shopify_identifiers) for r in records), [])


class Template(IdentifiersMixin, metaclass=PoolMeta):
    __name__ = 'product.template'

    shopify_uom = fields.Many2One(
        'product.uom', "Shopify UoM",
        states={
            'readonly': Bool(Eval('shopify_identifiers', [-1])),
            'invisible': ~Eval('salable', False),
            },
        depends={'default_uom_category'},
        help="The Unit of Measure of the product on Shopify.")

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.update([
                'name', 'web_shop_description', 'attribute_set',
                'customs_category', 'tariff_codes_category',
                'country_of_origin'])
        categories = cls._shopify_uom_categories()
        cls.shopify_uom.domain = [
            ('category', 'in', [Eval(c, -1) for c in categories]),
            ('digits', '=', 0),
            ]

    @classmethod
    def _shopify_uom_categories(cls):
        return ['default_uom_category']

    def get_shopify_uom(self):
        return self.sale_uom

    @classmethod
    def get_shopify_identifier_to_update(cls, templates):
        pool = Pool()
        Product = pool.get('product.product')
        products = [p for t in templates for p in t.products]
        return (super().get_shopify_identifier_to_update(templates)
            + Product.get_shopify_identifier_to_update(products))

    def get_shopify(self, shop):
        shopify_id = self.get_shopify_identifier(shop)
        product = None
        if shopify_id:
            try:
                product = shopify.Product.find(shopify_id)
            except pyactiveresource.connection.ResourceNotFound:
                pass
        if product is None:
            product = shopify.Product()
        product.title = self.name
        product.body_html = self.web_shop_description
        options = []
        for attribute in self.shopify_attributes:
            options.append({'name': attribute.string})
        product.options = options[:3] or [{'name': "Title"}]
        return product

    def get_shopify_metafields(self, shop):
        return {}

    @property
    def shopify_attributes(self):
        if not self.attribute_set:
            return []
        return filter(None, [
                self.attribute_set.shopify_option1,
                self.attribute_set.shopify_option2,
                self.attribute_set.shopify_option3])


class Template_SaleSecondaryUnit(metaclass=PoolMeta):
    __name__ = 'product.template'

    @classmethod
    def _shopify_uom_categories(cls):
        return super()._shopify_uom_categories() + [
            'sale_secondary_uom_category']

    def get_shopify_uom(self):
        uom = super().get_shopify_uom()
        if self.sale_secondary_uom and not self.sale_secondary_uom.digits:
            uom = self.sale_secondary_uom
        return uom


class Product(IdentifiersMixin, metaclass=PoolMeta):
    __name__ = 'product.product'

    shopify_sku = fields.Function(
        fields.Char("SKU"), 'get_shopify_sku', searcher='search_shopify_sku')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.update([
                'code', 'weight', 'weight_uom', 'attributes'])

    @classmethod
    def get_shopify_identifier_to_update(cls, records):
        pool = Pool()
        InventoryItem = pool.get('product.shopify_inventory_item')
        items = InventoryItem.browse(records)
        return (super().get_shopify_identifier_to_update(records)
            + sum((list(i.shopify_identifiers) for i in items), []))

    def set_shopify_identifier(self, web_shop, identifier=None):
        pool = Pool()
        InventoryItem = pool.get('product.shopify_inventory_item')
        if not identifier:
            inventory_item = InventoryItem(self.id)
            inventory_item.set_shopify_identifier(web_shop)
        return super().set_shopify_identifier(web_shop, identifier=identifier)

    def get_shopify_sku(self, name):
        return self.code

    @classmethod
    def search_shopify_sku(cls, name, clause):
        return [('code',) + tuple(clause[1:])]

    def get_shopify(
            self, shop, price, tax, shop_taxes_included=True,
            shop_weight_unit=None):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        Uom = pool.get('product.uom')
        shopify_id = self.get_shopify_identifier(shop)
        variant = None
        if shopify_id:
            try:
                variant = shopify.Variant.find(shopify_id)
            except pyactiveresource.connection.ResourceNotFound:
                pass
        if variant is None:
            variant = shopify.Variant()
        product_id = self.template.get_shopify_identifier(shop)
        if product_id is not None:
            variant.product_id = product_id
        variant.sku = self.shopify_sku
        price = self.shopify_price(
            price, tax, taxes_included=shop_taxes_included)
        if price is not None:
            variant.price = str(price.quantize(Decimal('.00')))
        else:
            variant.price = None
        variant.taxable = bool(tax)
        for identifier in self.identifiers:
            if identifier.type == 'ean':
                variant.barcode = identifier.code
                break
        for i, attribute in enumerate(self.template.shopify_attributes, 1):
            if self.attributes:
                value = self.attributes.get(attribute.name)
            else:
                value = None
            value = attribute.format(value)
            setattr(variant, 'option%i' % i, value)
        if getattr(self, 'weight', None) and shop_weight_unit:
            units = {}
            units['kg'] = ModelData.get_id('product', 'uom_kilogram')
            units['g'] = ModelData.get_id('product', 'uom_gram')
            units['lb'] = ModelData.get_id('product', 'uom_pound')
            units['oz'] = ModelData.get_id('product', 'uom_ounce')
            weight = self.weight
            weight_unit = self.weight_uom
            if self.weight_uom.id not in units.values():
                weight_unit = Uom(units[shop_weight_unit])
                weight = Uom.compute_qty(self.weight_uom, weight, weight_unit)
            variant.weight = weight
            variant.weight_unit = {
                v: k for k, v in units.items()}[weight_unit.id]
        for image in getattr(self, 'images_used', []):
            if image.web_shop:
                variant.image_id = image.get_shopify_identifier(shop)
                break
        else:
            variant.image_id = None
        return variant

    def get_shopify_metafields(self, shop):
        return {}

    def shopify_price(self, price, tax, taxes_included=True):
        pool = Pool()
        Uom = pool.get('product.uom')
        if price is None or tax is None:
            return None
        if taxes_included:
            price += tax
        return Uom.compute_price(
            self.sale_uom, price, self.shopify_uom,
            factor=self.shopify_uom_factor, rate=self.shopify_uom_rate)

    @property
    def shopify_uom_factor(self):
        return None

    @property
    def shopify_uom_rate(self):
        return None

    @property
    def shopify_quantity(self):
        pool = Pool()
        Uom = pool.get('product.uom')
        quantity = self.forecast_quantity
        if quantity < 0:
            quantity = 0
        return Uom.compute_qty(
            self.default_uom, quantity, self.shopify_uom, round=True,
            factor=self.shopify_uom_factor, rate=self.shopify_uom_rate)

    @classmethod
    def write(cls, *args):
        actions = iter(args)
        for products, values in zip(actions, actions):
            if 'template' in values:
                for product in products:
                    if (product.template.id != values.get('template')
                            and product.shopify_identifiers):
                        raise AccessError(gettext(
                                'web_shop_shopify.msg_product_change_template',
                                product=product.rec_name))
        super().write(*args)


class ShopifyInventoryItem(IdentifiersMixin, ModelSQL, ModelView):
    "Shopify Inventory Item"
    __name__ = 'product.shopify_inventory_item'

    product = fields.Function(
        fields.Many2One('product.product', "Product"), 'get_product')

    @classmethod
    def table_query(cls):
        return Pool().get('product.product').__table__()

    def get_product(self, name):
        return self.id

    def get_shopify(self, shop):
        pool = Pool()
        Product = pool.get('product.product')
        Move = pool.get('stock.move')
        # TODO: replace with product_types from sale line
        move_types = Move.get_product_types()

        shopify_id = self.get_shopify_identifier(shop)
        inventory_item = None
        if shopify_id:
            try:
                inventory_item = shopify.InventoryItem.find(shopify_id)
            except pyactiveresource.connection.ResourceNotFound:
                pass
        if inventory_item is None:
            product = Product(self.id)
            variant_id = product.get_shopify_identifier(shop)
            if not variant_id:
                return
            try:
                variant = shopify.Variant.find(variant_id)
            except pyactiveresource.connection.ResourceNotFound:
                return
            inventory_item = shopify.InventoryItem.find(
                variant.inventory_item_id)
        inventory_item.tracked = (
            self.product.type in move_types and not self.product.consumable)
        inventory_item.requires_shipping = self.product.type in move_types
        return inventory_item


class ShopifyInventoryItem_Customs(metaclass=PoolMeta):
    __name__ = 'product.shopify_inventory_item'

    def get_shopify(self, shop):
        pool = Pool()
        Date = pool.get('ir.date')
        inventory_item = super().get_shopify(shop)
        if inventory_item:
            with Transaction().set_context(company=shop.company.id):
                today = Date.today()
            inventory_item.country_code_of_origin = (
                self.product.country_of_origin.code
                if self.product.country_of_origin else None)
            tariff_code = self.product.get_tariff_code(
                {'date': today, 'country': None})
            inventory_item.harmonized_system_code = (
                tariff_code.code if tariff_code else None)
            country_harmonized_system_codes = []
            countries = set()
            for tariff_code in self.product.get_tariff_codes({'date': today}):
                if (tariff_code.country
                        and tariff_code.country not in countries):
                    country_harmonized_system_codes.append({
                            'harmonized_system_code': tariff_code.code,
                            'country_code': tariff_code.country.code,
                            })
                    countries.add(tariff_code.country)
            inventory_item.country_harmonized_system_codes = (
                country_harmonized_system_codes)
        return inventory_item


class Product_TariffCode(IdentifiersUpdateMixin, metaclass=PoolMeta):
    __name__ = 'product-customs.tariff.code'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.update(['product', 'tariff_code'])

    @classmethod
    def create(cls, vlist):
        identifiers = super().create(vlist)
        cls.set_shopify_to_update(identifiers)
        return identifiers

    @classmethod
    def delete(cls, records):
        cls.set_shopify_to_update(records)
        super().delete(records)

    @classmethod
    def get_shopify_identifier_to_update(cls, records):
        pool = Pool()
        Template = pool.get('product.template')
        Category = pool.get('product.category')
        templates = set()
        categories = set()
        for record in records:
            if isinstance(record.product, Template):
                templates.add(record.product)
            elif isinstance(record.product, Category):
                categories.add(record.product)
        if categories:
            for sub_categories in grouped_slice(list(categories)):
                templates.update(Template.search([
                            ('customs_category', 'in',
                                [c.id for c in sub_categories]),
                            ]))
        templates = Template.browse(list(templates))
        return Template.get_shopify_identifier_to_update(templates)


class ProductIdentifier(IdentifiersUpdateMixin, metaclass=PoolMeta):
    __name__ = 'product.identifier'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.update(['product', 'code'])

    @classmethod
    def create(cls, vlist):
        identifiers = super().create(vlist)
        cls.set_shopify_to_update(identifiers)
        return identifiers

    @classmethod
    def delete(cls, identifiers):
        cls.set_shopify_to_update(identifiers)
        super().delete(identifiers)

    @classmethod
    def get_shopify_identifier_to_update(cls, identifiers):
        return sum((
                list(i.product.shopify_identifiers) for i in identifiers), [])


class Product_SaleSecondaryUnit(metaclass=PoolMeta):
    __name__ = 'product.product'

    @property
    def shopify_uom_factor(self):
        factor = super().shopify_uom_factor
        if (self.sale_secondary_uom
                and self.shopify_uom.category
                == self.sale_secondary_uom.category):
            factor = self.sale_secondary_uom_normal_factor
        return factor

    @property
    def shopify_uom_rate(self):
        rate = super().shopify_uom_rate
        if (self.sale_secondary_uom
                and self.shopify_uom.category
                == self.sale_secondary_uom.category):
            rate = self.sale_secondary_uom_normal_rate
        return rate


class AttributeSet(IdentifiersUpdateMixin, metaclass=PoolMeta):
    __name__ = 'product.attribute.set'

    shopify_option1 = fields.Many2One(
        'product.attribute', "Option 1",
        domain=[
            ('id', 'in', Eval('attributes', [])),
            If(Eval('shopify_option2'),
                ('id', '!=', Eval('shopify_option2')),
                ()),
            If(Eval('shopify_option3'),
                ('id', '!=', Eval('shopify_option3')),
                ()),
            ])
    shopify_option2 = fields.Many2One(
        'product.attribute', "Option 2",
        domain=[
            ('id', 'in', Eval('attributes', [])),
            If(Eval('shopify_option1'),
                ('id', '!=', Eval('shopify_option1')),
                ('id', '=', None)),
            If(Eval('shopify_option3'),
                ('id', '!=', Eval('shopify_option3')),
                ()),
            ],
        states={
            'invisible': ~Eval('shopify_option1'),
            })
    shopify_option3 = fields.Many2One(
        'product.attribute', "Option 3",
        domain=[
            ('id', 'in', Eval('attributes', [])),
            If(Eval('shopify_option1'),
                ('id', '!=', Eval('shopify_option1')),
                ()),
            If(Eval('shopify_option2'),
                ('id', '!=', Eval('shopify_option2')),
                ('id', '=', None)),
            ],
        states={
            'invisible': ~Eval('shopify_option2'),
            })

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.update(
            ['shopify_option1', 'shopify_option2', 'shopify_option3'])

    @classmethod
    def get_shopify_identifier_to_update(cls, sets):
        pool = Pool()
        Template = pool.get('product.template')
        templates = []
        for sub_sets in grouped_slice(sets):
            templates.extend(Template.search([
                        ('attribute_set', 'in', [s.id for s in sub_sets]),
                        ]))
        return Template.get_shopify_identifier_to_update(templates)


class Attribute(IdentifiersUpdateMixin, metaclass=PoolMeta):
    __name__ = 'product.attribute'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.add('selection')
        domain = [
            ('type', '!=', 'shopify'),
            ]
        if cls.web_shops.domain:
            cls.web_shops.domain = [cls.web_shops.domain, domain]
        else:
            cls.web_shops.domain = domain

    @classmethod
    def get_shopify_identifier_to_update(cls, attributes):
        pool = Pool()
        Set = pool.get('product.attribute.set')
        sets = Set.browse(sum((a.sets for a in attributes), ()))
        return Set.get_shopify_identifier_to_update(sets)


class Image(IdentifiersMixin, metaclass=PoolMeta):
    __name__ = 'product.image'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._shopify_fields.update(['template', 'product', 'attributes'])

    @classmethod
    def create(cls, vlist):
        images = super().create(vlist)
        cls.set_shopify_to_update(images)
        return images

    @classmethod
    def write(cls, *args):
        pool = Pool()
        Identifier = pool.get('web.shop.shopify_identifier')
        actions = iter(args)
        to_delete = []
        for images, values in zip(actions, actions):
            if values.keys() & {'image', 'template', 'web_shop'}:
                for image in images:
                    to_delete.extend(image.shopify_identifiers)
        super().write(*args)
        Identifier.delete(to_delete)

    @classmethod
    def delete(cls, images):
        cls.set_shopify_to_update(images)
        super().delete(images)

    @classmethod
    def get_shopify_identifier_to_update(cls, images):
        return (
            sum((list(i.template.shopify_identifiers) for i in images), [])
            + sum(
                (list(p.shopify_identifiers)
                    for i in images for p in i.template.products), []))

    def get_shopify(self, shop):
        shopify_id = self.get_shopify_identifier(shop)
        product_id = self.template.get_shopify_identifier(shop)
        product_image = None
        if shopify_id and product_id:
            try:
                product_image = shopify.Image.find(
                    shopify_id, product_id=product_id)
            except pyactiveresource.connection.ResourceNotFound:
                pass
        if product_image is None:
            product_image = shopify.Image()
            product_image.attach_image(
                self.image, filename=slugify(self.shopify_name))
        product_image.product_id = self.template.get_shopify_identifier(shop)
        product_image.alt = self.shopify_name
        return product_image

    @property
    def shopify_name(self):
        if self.product:
            return self.product.name
        else:
            return self.template.name


class Image_Attribute(metaclass=PoolMeta):
    __name__ = 'product.image'

    @property
    def shopify_name(self):
        name = super().shopify_name
        if self.product:
            attributes_name = self.product.attributes_name
        else:
            attributes_name = self.attributes_name
        if attributes_name:
            name += ' ' + attributes_name
        return name
