# 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
from itertools import groupby

from trytond.i18n import gettext
from trytond.model import ModelView, Workflow, fields
from trytond.modules.product import round_price
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, If
from trytond.transaction import Transaction

from .exceptions import PurchaseWarning


class Routing(metaclass=PoolMeta):
    __name__ = 'production.routing'

    supplier = fields.Many2One(
        'party.party', "Supplier",
        help="The supplier to outsource the production.")
    supplier_service = fields.Many2One(
        'product.product', "Service",
        ondelete='RESTRICT',
        domain=[
            ('purchasable', '=', True),
            ('template.type', '=', 'service'),
            ],
        states={
            'required': Bool(Eval('supplier')),
            'invisible': ~Eval('supplier'),
            },
        depends={'supplier_service_supplier'},
        help="The service to buy to the supplier for the production.")
    supplier_service_supplier = fields.Many2One(
        'purchase.product_supplier', "Supplier's Service",
        ondelete='RESTRICT',
        domain=[
            ('template.type', '=', 'service'),
            If(Bool('supplier_service'),
                ['OR',
                    [
                        ('template.products', '=', Eval('supplier_service')),
                        ('product', '=', None),
                        ],
                    ('product', '=', Eval('supplier_service')),
                    ],
                []),
            ('party', '=', Eval('supplier', -1)),
            ],
        states={
            'invisible': ~Eval('supplier'),
            },
        help="The supplier's service to buy for the production.")
    supplier_quantity = fields.Float("Quantity",
        states={
            'invisible': ~Eval('supplier_service'),
            'required': Bool(Eval('supplier_service')),
            },
        help="The quantity to buy to produce one time the BOM.")

    @classmethod
    def default_supplier_quantity(cls):
        return 1

    @fields.depends('supplier')
    def _get_product_supplier_pattern(self):
        return {
            'party': self.supplier.id if self.supplier else -1,
            }

    @fields.depends('supplier', 'supplier_service',
        'supplier_service_supplier')
    def on_change_supplier_service(self):
        if self.supplier_service:
            product_suppliers = list(
                self.supplier_service.product_suppliers_used(
                    **self._get_product_supplier_pattern()))
            if len(product_suppliers) == 1:
                self.supplier_service_supplier, = product_suppliers
            elif (self.supplier_service_supplier
                    and (self.supplier_service_supplier
                        not in product_suppliers)):
                self.supplier_service = None

    @fields.depends('supplier_service', 'supplier_service_supplier')
    def on_change_supplier_service_supplier(self):
        if self.supplier_service_supplier:
            if self.supplier_service_supplier.product:
                self.supplier_service = self.supplier_service_supplier.product
            elif not self.supplier_service:
                products = self.supplier_service_supplier.template.products
                if len(products) == 1:
                    self.supplier_service, = products

    @classmethod
    def view_attributes(cls):
        return super(Routing, cls).view_attributes() + [
            ('//page[@id="supplier"]', 'states', {
                    'invisible': ~Eval('supplier'),
                    }),
            ]


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

    purchase_lines = fields.One2Many(
        'purchase.line', 'production', "Purchase Lines",
        domain=[
            ('purchase.company', '=', Eval('company', -1)),
            ],
        help="The lines to add to the production cost.")

    def get_cost(self, name):
        pool = Pool()
        Currency = pool.get('currency.currency')
        cost = super(Production, self).get_cost(name)
        for line in self.purchase_lines:
            if line.purchase.state != 'cancelled' and line.amount:
                cost += Currency.compute(
                    line.purchase.currency, line.amount,
                    self.company.currency, round=False)
        return round_price(cost)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, productions):
        pool = Pool()
        PurchaseLine = pool.get('purchase.line')
        super(Production, cls).draft(productions)
        PurchaseLine.delete([l for p in productions for l in p.purchase_lines
                if l.purchase_state in {'draft', 'cancelled'}])

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, productions):
        pool = Pool()
        PurchaseLine = pool.get('purchase.line')
        super(Production, cls).cancel(productions)
        PurchaseLine.delete([l for p in productions for l in p.purchase_lines
                if l.purchase_state in {'draft', 'cancelled'}])

    @classmethod
    @ModelView.button
    @Workflow.transition('waiting')
    def wait(cls, productions):
        pool = Pool()
        Purchase = pool.get('purchase.purchase')
        PurchaseLine = pool.get('purchase.line')
        Date = pool.get('ir.date')

        draft_productions = [p for p in productions if p.state == 'draft']

        super(Production, cls).wait(productions)

        purchases = []
        lines = []

        def has_supplier(production):
            return production.routing and production.routing.supplier
        productions = cls.browse(sorted(
                filter(has_supplier, draft_productions),
                key=cls._group_purchase_key))
        for key, grouped in groupby(productions, key=cls._group_purchase_key):
            productions = list(grouped)
            key = dict(key)
            with Transaction().set_context(company=key['company'].id):
                today = Date.today()
            try:
                purchase_date = min(p.planned_start_date or p.planned_date
                    for p in productions if p.planned_date)
            except ValueError:
                purchase_date = today
            if purchase_date < today:
                purchase_date = today
            purchase = Purchase(purchase_date=purchase_date)
            for f, v in key.items():
                setattr(purchase, f, v)
            purchases.append(purchase)
            for production in productions:
                line = production._get_purchase_line(purchase)
                line.purchase = purchase
                line.production = production
                lines.append(line)
        Purchase.save(purchases)
        PurchaseLine.save(lines)

    def _group_purchase_key(self):
        supplier = self.routing.supplier
        if self.routing.supplier_service_supplier:
            currency = self.routing.supplier_service_supplier.currency
        else:
            currency = self.company.currency
        return (
            ('company', self.company),
            ('party', supplier),
            ('payment_term', supplier.supplier_payment_term),
            ('warehouse', self.warehouse),
            ('currency', currency),
            ('invoice_address', supplier.address_get(type='invoice')),
            )

    def _get_purchase_line(self, purchase):
        pool = Pool()
        Line = pool.get('purchase.line')
        line = Line()
        line.product = self.routing.supplier_service
        line.product_supplier = self.routing.supplier_service_supplier
        line.unit = self.routing.supplier_service.purchase_uom
        factor = self.bom.compute_factor(
            self.product, self.quantity or 0, self.unit)
        line.quantity = line.unit.round(
            factor * self.routing.supplier_quantity)
        line.purchase = purchase
        line.on_change_product()
        if line.unit_price is None:
            line.unit_price = round_price(Decimal(0))
        return line

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def do(cls, productions):
        pool = Pool()
        Warning = pool.get('res.user.warning')

        def pending_purchase(production):
            return any(l.purchase_state in {'draft', 'quotation'}
                for l in production.purchase_lines)
        pendings = list(filter(pending_purchase, productions))
        if pendings:
            names = ', '.join(p.rec_name for p in productions[:5])
            if len(pendings) > 5:
                names += '...'
            warning_name = Warning.format('pending_purchase.done', pendings)
            if Warning.check(warning_name):
                raise PurchaseWarning(
                    warning_name,
                    gettext('production_outsourcing.msg_pending_purchase_done',
                        productions=names))
        super().do(productions)
