from odoo import api, fields, models, _
from odoo.exceptions import UserError


class StockQuantPackage(models.Model):
    _inherit = 'stock.quant.package'

    carrier_id = fields.Many2one('delivery.carrier', string='Carrier')
    carrier_tracking_ref = fields.Char(string='Tracking Reference')
    require_insurance = fields.Boolean(string='Require Insurance')
    require_signature = fields.Boolean(string='Require Signature')
    declared_value = fields.Float(string='Declared Value')

    def _get_active_picking(self):
        picking_id = self._context.get('active_id')
        picking_model = self._context.get('active_model')
        if not picking_id or picking_model != 'stock.picking':
            raise UserError('Cannot cancel package other than through shipment/picking.')
        return self.env['stock.picking'].browse(picking_id)

    def send_to_shipper(self):
        picking = self._get_active_picking()
        picking.with_context(packages=self).send_to_shipper()

    def cancel_shipment(self):
        picking = self._get_active_picking()
        picking.with_context(packages=self).cancel_shipment()


class StockPicking(models.Model):
    _inherit = 'stock.picking'

    shipping_account_id = fields.Many2one('partner.shipping.account', string='Shipping Account')
    commercial_partner_id = fields.Many2one('res.partner', related='partner_id.commercial_partner_id')
    require_insurance = fields.Selection([
            ('auto', 'Automatic'),
            ('yes', 'Yes'),
            ('no', 'No'),
        ], string='Require Insurance', default='auto',
        help='If your carrier supports it, auto should be calculated off of the "Automatic Insurance Value" field.')
    require_signature = fields.Selection([
            ('auto', 'Automatic'),
            ('yes', 'Yes'),
            ('no', 'No'),
        ], string='Require Signature', default='auto',
        help='If your carrier supports it, auto should be calculated off of the "Automatic Signature Required Value" field.')
    package_carrier_tracking_ref = fields.Char(string='Package Tracking Numbers', compute='_compute_package_carrier_tracking_ref')

    @api.depends('move_line_ids.package_id.carrier_tracking_ref')
    def _compute_package_carrier_tracking_ref(self):
        for picking in self:
            package_refs = picking.package_ids.filtered('carrier_tracking_ref').mapped('carrier_tracking_ref')
            if package_refs:
                picking.package_carrier_tracking_ref = ','.join(package_refs)
            else:
                picking.package_carrier_tracking_ref = False

    @api.onchange('carrier_id')
    def _onchange_carrier_id_for_priority(self):
        for picking in self:
            if picking.carrier_id and picking.carrier_id.procurement_priority:
                picking.priority = picking.carrier_id.procurement_priority

    @api.depends('move_lines.priority', 'carrier_id')
    def _compute_priority(self):
        with_carrier_priority = self.filtered(lambda p: p.carrier_id.procurement_priority)
        for picking in with_carrier_priority:
            picking.priority = picking.carrier_id.procurement_priority
        super(StockPicking, (self-with_carrier_priority))._compute_priority()

    @api.model
    def create(self, values):
        origin = values.get('origin')
        if origin and not values.get('shipping_account_id'):
            so = self.env['sale.order'].search([('name', '=', str(origin))], limit=1)
            if so and so.shipping_account_id:
                values['shipping_account_id'] = so.shipping_account_id.id
        carrier_id = values.get('carrier_id')
        if carrier_id:
            carrier = self.env['delivery.carrier'].browse(carrier_id)
            if carrier.procurement_priority:
                values['priority'] = carrier.procurement_priority

        res = super(StockPicking, self).create(values)
        return res

    def declared_value(self, package=None):
        self.ensure_one()
        if package:
            return package.declared_value
        cost = sum([(l.product_id.standard_price * l.qty_done) for l in self.move_line_ids] or [0.0])
        if not cost:
            # Assume Full Value
            cost = sum([(l.product_id.standard_price * l.product_uom_qty) for l in self.move_lines] or [0.0])
        return cost

    def clear_carrier_tracking_ref(self):
        self.write({'carrier_tracking_ref': False})

    def reset_carrier_tracking_ref(self):
        for picking in self:
            picking.carrier_tracking_ref = picking.package_carrier_tracking_ref

    # Override to send to specific packaging carriers
    def send_to_shipper(self):
        packages = self._context.get('packages')
        self.ensure_one()
        if not packages:
            packages = self.package_ids
        package_carriers = packages.mapped('carrier_id')
        if not package_carriers:
            # Original behavior
            return super().send_to_shipper()

        tracking_numbers = []
        carrier_prices = []
        order_currency = self.sale_id.currency_id or self.company_id.currency_id
        for carrier in package_carriers:
            self.carrier_id = carrier
            carrier_packages = packages.filtered(lambda p: p.carrier_id == carrier)
            res = carrier.send_shipping(self)
            if res:
                res = res[0]
                if carrier.free_over and self.sale_id and self.sale_id._compute_amount_total_without_delivery() >= carrier.amount:
                    res['exact_price'] = 0.0
                carrier_price = res['exact_price'] * (1.0 + (self.carrier_id.margin / 100.0))
                carrier_prices.append(carrier_price)
                tracking_number = ''
                if res['tracking_number']:
                    tracking_number = res['tracking_number']
                    tracking_numbers.append(tracking_number)
                    # Try to add tracking to the individual packages.
                    potential_tracking_numbers = tracking_number.split(',')
                    if len(potential_tracking_numbers) == 1:
                        potential_tracking_numbers = tracking_number.split('+')  # UPS for example...
                    if len(potential_tracking_numbers) >= len(carrier_packages):
                        for t, p in zip(potential_tracking_numbers, carrier_packages):
                            p.carrier_tracking_ref = t
                    else:
                        carrier_packages.write({'carrier_tracking_ref': tracking_number})
                msg = _("Shipment sent to carrier %s for shipping with tracking number %s<br/>Cost: %.2f %s") % (carrier.name, tracking_number, carrier_price, order_currency.name)
                self.message_post(body=msg)

        self.carrier_price = sum(carrier_prices or [0.0])
        self.carrier_tracking_ref = ','.join(tracking_numbers or [''])
        self._add_delivery_cost_to_so()

    # Override to provide per-package versions...
    def cancel_shipment(self):
        packages = self._context.get('packages')
        pickings_with_package_tracking = self.filtered(lambda p: p.package_carrier_tracking_ref)
        for picking in pickings_with_package_tracking:
            if packages:
                current_packages = packages
            else:
                current_packages = picking.package_ids
            # Packages without a carrier can just be cleared
            packages_without_carrier = current_packages.filtered(lambda p: not p.carrier_id and p.carrier_tracking_ref)
            packages_without_carrier.write({
                'carrier_tracking_ref': False,
            })
            # Packages with carrier can use the carrier method
            packages_with_carrier = current_packages.filtered(lambda p: p.carrier_id and p.carrier_tracking_ref)
            carriers = packages_with_carrier.mapped('carrier_id')
            for carrier in carriers:
                carrier_packages = packages_with_carrier.filtered(lambda p: p.carrier_id == carrier)
                carrier.cancel_shipment(self, packages=carrier_packages)
                package_refs = ','.join(carrier_packages.mapped('carrier_tracking_ref'))
                msg = "Shipment %s cancelled" % package_refs
                picking.message_post(body=msg)
                carrier_packages.write({'carrier_tracking_ref': False})

        pickings_without_package_tracking = self - pickings_with_package_tracking
        if pickings_without_package_tracking:
            # use original on these
            super(StockPicking, pickings_without_package_tracking).cancel_shipment()


class StockMove(models.Model):
    _inherit = 'stock.move'

    def _prepare_procurement_values(self):
        res = super(StockMove, self)._prepare_procurement_values()
        res['priority'] = self.picking_id.priority or self.priority
        return res
