# 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 collections import defaultdict
from functools import wraps

from trytond.i18n import gettext
from trytond.model import Model, ModelView, Workflow
from trytond.model.exceptions import AccessError
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice
from trytond.transaction import Transaction, without_check_access


def process_sale_supply(func):
    @wraps(func)
    def wrapper(cls, productions):
        pool = Pool()
        Sale = pool.get('sale.sale')
        transaction = Transaction()
        context = transaction.context

        sales = set()
        with without_check_access():
            for sub_productions in grouped_slice(productions):
                ids = [p.id for p in sub_productions]
                sales.update([s.id for s in Sale.search([
                                ('lines.productions', 'in', ids),
                                ])])
        result = func(cls, productions)
        if sales:
            with transaction.set_context(
                    queue_batch=context.get('queue_batch', True)):
                Sale.__queue__.process(sales)
        return result
    return wrapper


class Production(metaclass=PoolMeta):
    __name__ = 'production'

    @classmethod
    def _get_origin(cls):
        return super()._get_origin() | {'sale.line'}

    @classmethod
    @process_sale_supply
    def on_delete(cls, productions):
        return super().on_delete(productions)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    @process_sale_supply
    def cancel(cls, productions):
        super().cancel(productions)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, productions):
        pool = Pool()
        SaleLine = pool.get('sale.line')
        for production in productions:
            if (production.state == 'cancelled'
                    and isinstance(production.origin, SaleLine)):
                raise AccessError(
                    gettext('sale_supply_production'
                        '.msg_production_reset_draft',
                        production=production.rec_name))
        super().draft(productions)

    @classmethod
    @ModelView.button
    @Workflow.transition('running')
    @process_sale_supply
    def run(cls, productions):
        super().run(productions)

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    @process_sale_supply
    def do(cls, productions):
        super().do(productions)

        for production in productions:
            production.assign_supplied()

    def assign_supplied(self, grouping=('product',), filter_=None):
        pool = Pool()
        SaleLine = pool.get('sale.line')

        if isinstance(self.origin, SaleLine):
            sale_line = self.origin
        else:
            return

        def filter_func(move):
            if filter_ is None:
                return True
            for fieldname, values in filter_:
                value = getattr(move, fieldname)
                if isinstance(value, Model):
                    value = value.id
                if value not in values:
                    return False

        def get_key(move):
            key = (move.to_location.id,)
            for field in grouping:
                value = getattr(move, field)
                if isinstance(value, Model):
                    value = value.id
                key += (value,)
            return key

        pbl = defaultdict(lambda: defaultdict(int))
        for move in filter(filter_func, self.outputs):
            pbl[move.product][get_key(move)] += move.internal_quantity
        sale_line.assign_supplied(pbl[sale_line.product], grouping=grouping)
