# 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 trytond.model import ModelSQL, ModelView, fields, sequence_ordered
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, If
from trytond.transaction import Transaction


class Sale(metaclass=PoolMeta):
    __name__ = 'sale.sale'

    _states = {
        'readonly': Eval('state') != 'draft',
        }

    carriages = fields.One2Many(
        'sale.carriage', 'sale', "Carriages", readonly=True)
    before_carriages = fields.One2Many(
        'sale.carriage', 'sale', "Before Carriages",
        filter=[
            ('type', '=', 'before'),
            ],
        states=_states,
        help="Carriages before the main carrier.")
    after_carriages = fields.One2Many(
        'sale.carriage', 'sale', "After Carriages",
        filter=[
            ('type', '=', 'after'),
            ],
        states=_states,
        help="Carriages after the main carrier.")

    del _states

    def set_shipment_cost(self):
        removed = super().set_shipment_cost()
        lines = list(self.lines or [])
        for carriage in self.carriages:
            if carriage.cost_method:
                cost = self.compute_shipment_cost(carriage.carrier)
                if cost is not None:
                    unit_price = None
                    for line in removed:
                        if line.shipment_cost == cost:
                            unit_price = (
                                line.unit_price * Decimal(str(line.quantity)))
                    lines.append(self.get_shipment_cost_line(
                            carriage.carrier, cost, unit_price=unit_price))
        self.lines = lines
        return removed

    def _get_shipment_sale(self, Shipment, key):
        pool = Pool()
        ShipmentCarriage = pool.get('stock.shipment.carriage')
        ShipmentOut = pool.get('stock.shipment.out')
        shipment = super()._get_shipment_sale(Shipment, key)
        if isinstance(shipment, ShipmentOut):
            shipment.carriages = [
                ShipmentCarriage.from_sale(carriage)
                for carriage in self.carriages]
        return shipment

    @fields.depends('before_carriages', 'after_carriages')
    def _get_incoterm_pattern(self):
        pattern = super()._get_incoterm_pattern()

        def cost_methods(carriages):
            for carriage in carriages:
                yield getattr(carriage, 'cost_method', None)

        def negate(iterable):
            for i in iterable:
                yield not i

        if self.before_carriages:
            if any(cost_methods(self.before_carriages)):
                pattern['before_carrier'] = 'seller'
            elif all(negate(cost_methods(self.before_carriages))):
                pattern['before_carrier'] = 'buyer'

        if self.after_carriages:
            if any(cost_methods(self.after_carriages)):
                pattern['after_carrier'] = 'seller'
            elif all(negate(cost_methods(self.after_carriages))):
                pattern['after_carrier'] = 'buyer'
        return pattern

    @classmethod
    def copy(cls, sales, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('before_carriages')
        default.setdefault('after_carriages')
        return super().copy(sales, default=default)

    @property
    def _cost_shipments(self):
        shipments = super()._cost_shipments
        for shipment in self.shipments:
            shipments.extend(shipment.carriages)
        return shipments


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

    def get_invoice_line(self):
        context = Transaction().context
        shipment_cost_invoiced = context.get('_shipment_cost_invoiced')
        if shipment_cost_invoiced is not None:
            # Make a copy to still detect new shipment after super call
            shipment_cost_invoiced = list(shipment_cost_invoiced)
        lines = super().get_invoice_line()
        if (self.shipment_cost
                and shipment_cost_invoiced is not None):
            for shipment in self.sale.shipments:
                if (shipment.state == 'done'
                        and shipment.id not in shipment_cost_invoiced):
                    invoice = self.sale._get_invoice()
                    for carriage in shipment.carriages:
                        # XXX: self is not necessary linked to carriage
                        invoice_line = carriage.get_cost_sale_invoice_line(
                            invoice, origin=self)
                        if invoice_line:
                            lines.append(invoice_line)
        return lines


class Carriage(sequence_ordered(), ModelSQL, ModelView):
    __name__ = 'sale.carriage'

    _states = {
        'readonly': Eval('sale_state') != 'draft',
        }

    sale = fields.Many2One(
        'sale.sale', "Sale",
        required=True, ondelete='CASCADE', states=_states)
    type = fields.Selection([
            ('before', "Before"),
            ('after', "After"),
            ], "Type", sort=False, required=True, states=_states)
    carrier = fields.Many2One(
        'carrier', "Carrier", required=True,
        domain=[
            If(Eval('sale_state') == 'draft', [
                    ('carrier_product.salable', '=', True),
                    ('id', 'in', Eval('available_carriers', [])),
                    ],
                []),
            ],
        states=_states)
    cost_method = fields.Selection(
        'get_cost_methods', "Cost Method", states=_states)
    from_ = fields.Many2One('party.address', "From", states=_states)
    to = fields.Many2One('party.address', "To", states=_states)

    sale_state = fields.Function(
        fields.Selection('get_sale_states', "Sale State"),
        'on_change_with_sale_state')
    available_carriers = fields.Function(
        fields.Many2Many('carrier', None, None, "Available Carriers"),
        'on_change_with_available_carriers')

    del _states

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('sale')
        cls._order.insert(0, ('type', 'ASC'))

    @classmethod
    def get_cost_methods(cls):
        pool = Pool()
        Sale = pool.get('sale.sale')
        fieldname = 'shipment_cost_method'
        return Sale.fields_get([fieldname])[fieldname]['selection']

    @classmethod
    def get_sale_states(cls):
        pool = Pool()
        Sale = pool.get('sale.sale')
        return Sale.fields_get(['state'])['state']['selection']

    @fields.depends('sale', '_parent_sale.state')
    def on_change_with_sale_state(self, name=None):
        if self.sale:
            return self.sale.state

    @classmethod
    def default_available_carriers(cls):
        pool = Pool()
        CarrierSelection = pool.get('carrier.selection')
        carriers = CarrierSelection.get_carriers({})
        return [c.id for c in carriers]

    @fields.depends(methods=['_get_carrier_selection_pattern'])
    def on_change_with_available_carriers(self, name=None):
        pool = Pool()
        CarrierSelection = pool.get('carrier.selection')

        pattern = self._get_carrier_selection_pattern()
        return CarrierSelection.get_carriers(pattern)

    @fields.depends('from_', 'to')
    def _get_carrier_selection_pattern(self):
        pattern = {}
        if self.from_ and self.from_.country:
            pattern['from_country'] = self.from_.country.id
        else:
            pattern['from_country'] = None
        if self.to and self.to.country:
            pattern['to_country'] = self.to.country.id
        else:
            pattern['to_country'] = None
        return pattern

    @fields.depends(methods=['_on_change_pattern'])
    def on_change_from_(self):
        self._on_change_pattern()

    @fields.depends(methods=['_on_change_pattern'])
    def on_change_to(self):
        self._on_change_pattern()

    @fields.depends('carrier', methods=['on_change_with_available_carriers'])
    def _on_change_pattern(self):
        self.available_carriers = self.on_change_with_available_carriers()
        if self.carrier and self.carrier not in self.available_carriers:
            self.carrier = None

    def get_rec_name(self, name):
        return f'{self.carrier.rec_name} @ {self.sale.rec_name}'
