# 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 trytond.i18n import gettext
from trytond.model import ModelSQL, ValueMixin, fields
from trytond.model.exceptions import RequiredValidationError
from trytond.modules.product import round_price
from trytond.modules.purchase.purchase import (
    get_shipments_returns, search_shipments_returns)
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction

purchase_drop_location = fields.Many2One(
    'stock.location', "Purchase Drop Location", domain=[('type', '=', 'drop')])


class Request(metaclass=PoolMeta):
    __name__ = 'purchase.request'

    customer = fields.Many2One('party.party', 'Customer', readonly=True,
        states={
            'invisible': ~Eval('customer'),
            },
        context={
            'company': Eval('company', -1),
            },
        depends={'company'})
    delivery_address = fields.Many2One('party.address', 'Delivery Address',
        domain=[('party', '=', Eval('customer', -1))],
        states={
            'invisible': ~Eval('customer'),
            'readonly': Eval('state') != 'draft',
            })


class Configuration(metaclass=PoolMeta):
    __name__ = 'purchase.configuration'

    purchase_drop_location = fields.MultiValue(purchase_drop_location)

    @classmethod
    def default_purchase_drop_location(cls, **pattern):
        return cls.multivalue_model(
            'purchase_drop_location').default_purchase_drop_location()


class ConfigurationPurchaseDropLocation(ModelSQL, ValueMixin):
    __name__ = 'purchase.configuration.purchase_drop_location'
    purchase_drop_location = purchase_drop_location

    @classmethod
    def default_purchase_drop_location(cls):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        try:
            return ModelData.get_id(
                'sale_supply_drop_shipment', 'location_drop')
        except KeyError:
            return None


class Purchase(metaclass=PoolMeta):
    __name__ = 'purchase.purchase'

    customer = fields.Many2One('party.party', 'Customer', readonly=True,
        states={
            'invisible': ~Eval('customer'),
            },
        context={
            'company': Eval('company', -1),
            },
        depends={'company'})
    delivery_address = fields.Many2One('party.address', 'Delivery Address',
        domain=[('party', '=', Eval('customer', -1))],
        states={
            'readonly': Eval('state') != 'draft',
            'invisible': ~Eval('customer'),
            })
    drop_shipments = fields.Function(fields.Many2Many('stock.shipment.drop',
            None, None, "Drop Shipments",
            states={
                'invisible': ~Eval('customer'),
                }),
        'get_drop_shipments', searcher='search_drop_shipments')
    drop_location = fields.Many2One('stock.location', 'Drop Location',
        domain=[('type', '=', 'drop')],
        states={
            'invisible': ~Eval('customer', False),
            'required': Eval('customer', False),
            })

    @staticmethod
    def default_drop_location():
        pool = Pool()
        PurchaseConfig = pool.get('purchase.configuration')

        config = PurchaseConfig(1)
        if config.purchase_drop_location:
            return config.purchase_drop_location.id

    @fields.depends('customer', 'delivery_address')
    def on_change_customer(self):
        self.delivery_address = None
        if self.customer:
            self.delivery_address = self.customer.address_get(type='delivery')

    @property
    def delivery_full_address(self):
        address = super().delivery_full_address
        if self.customer and self.delivery_address:
            address = self.delivery_address.full_address
        return address

    get_drop_shipments = get_shipments_returns('stock.shipment.drop')
    search_drop_shipments = search_shipments_returns('stock.shipment.drop')

    def check_for_quotation(self):
        super().check_for_quotation()
        if self.customer and not self.delivery_address:
            raise RequiredValidationError(
                gettext('sale_supply_drop_shipment'
                    '.msg_delivery_address_required_quotation_purchase') % {
                    'purchase': self.rec_name,
                    })

    @classmethod
    def _process_shipment(cls, purchases):
        pool = Pool()
        DropShipment = pool.get('stock.shipment.drop')

        drop_shipments = []
        for purchase in purchases:
            if purchase.customer:
                moves = purchase.create_move('in')
                if moves:
                    drop_shipment = purchase.create_drop_shipment()
                    drop_shipment.supplier_moves = moves
                    drop_shipments.append(drop_shipment)
        DropShipment.save(drop_shipments)
        DropShipment.wait(drop_shipments)
        super()._process_shipment(purchases)

    def create_drop_shipment(self):
        pool = Pool()
        DropShipment = pool.get('stock.shipment.drop')

        return DropShipment(
            company=self.company,
            supplier=self.party,
            contact_address=self.party.address_get(),
            customer=self.customer,
            delivery_address=self.delivery_address,
            )


class Line(metaclass=PoolMeta):
    __name__ = 'purchase.line'

    def get_to_location(self, name):
        result = super().get_to_location(name)
        if self.purchase.customer:
            return self.purchase.drop_location.id
        return result

    def get_move(self, move_type):
        pool = Pool()
        Currency = pool.get('currency.currency')
        move = super().get_move(move_type)
        if move and self.purchase.customer:
            cost_price = Currency.compute(
                move.currency, move.unit_price, move.company.currency)
            move.cost_price = round_price(cost_price)
        return move


class ProductSupplier(metaclass=PoolMeta):
    __name__ = 'purchase.product_supplier'

    drop_shipment = fields.Boolean('Drop Shipment',
        states={
            'invisible': ~Eval('drop_shipment_available', False),
            })
    drop_shipment_available = fields.Function(
        fields.Boolean("Drop Shipment Available"),
        'on_change_with_drop_shipment_available')

    @fields.depends('product', 'template',
        '_parent_product.type', '_parent_product.supply_on_sale',
        '_parent_template.type', '_parent_template.supply_on_sale')
    def on_change_with_drop_shipment_available(self, name=None):
        product = self.product or self.template
        if product and product.type in {'goods', 'assets'}:
            return bool(product.supply_on_sale)


class RequestCreatePurchase(metaclass=PoolMeta):
    __name__ = 'purchase.request.create_purchase'

    @classmethod
    def _group_purchase_key(cls, requests, request):
        result = super()._group_purchase_key(requests, request)
        result += (
            ('customer', request.customer.id if request.customer else None),
            ('delivery_address', request.delivery_address.id
                if request.delivery_address else None),
            )
        return result


class HandleShipmentException(metaclass=PoolMeta):
    __name__ = 'purchase.handle.shipment.exception'

    def transition_handle(self):
        pool = Pool()
        Sale = pool.get('sale.sale')
        Move = pool.get('stock.move')
        transaction = Transaction()
        context = transaction.context

        super().transition_handle()

        sales = set()
        moves = set()
        to_recreate = set(self.ask.recreate_moves)
        domain_moves = set(self.ask.domain_moves)

        for line in self.record.lines:
            if not set(line.moves) & domain_moves:
                continue
            if not any(m in to_recreate for m in line.moves):
                for request in line.requests:
                    for sale_line in request.sale_lines:
                        moves.update({m for m in sale_line.moves
                                if (m.state != 'done'
                                    and m.from_location.type == 'drop')})
                        sales.add(sale_line.sale)

        if moves:
            Move.cancel(moves)
        if sales:
            with transaction.set_context(
                    queue_batch=context.get('queue_batch', True)):
                Sale.__queue__.process(sales)
        return 'end'
