# -*- coding: utf-8 -*-
import logging
from collections import defaultdict, namedtuple
from odoo.addons.stock.models.stock_rule import ProcurementException
from re import findall as regex_findall
from re import split as regex_split
from odoo import SUPERUSER_ID, _, api, fields, models, registry
from odoo.tools import float_compare, float_is_zero, html_escape


class StockRule(models.Model):
    _inherit = 'stock.rule'

    @api.model
    def _run_pull(self, procurements):
        moves_values_by_company = defaultdict(list)
        mtso_products_by_locations = defaultdict(list)

        # To handle the `mts_else_mto` procure method, we do a preliminary loop to
        # isolate the products we would need to read the forecasted quantity,
        # in order to to batch the read. We also make a sanitary check on the
        # `location_src_id` field.

        # list1 = []
        # for item in procurements:
        #     num = int(item[0].product_qty)
        #     if num > 1:
        #         for no in range(1, num+1):
        #
        #             Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
        #                                                      'product_uom', 'location_id', 'name', 'origin',
        #                                                      'company_id',
        #                                                      'values'])
        #             s = Procurement(product_id=item[0].product_id,product_qty=1.0,product_uom=item[0].product_uom,
        #                             location_id=item[0].location_id,
        #                             name=item[0].name,
        #                             origin=item[0].origin,
        #                             company_id=item[0].company_id,
        #                             values=item[0].values,
        #                             )
        #             item1 = list(item)
        #             item1[0]=s
        #
        #             list1.append(tuple(item1))
        #     else:
        #         list1.append(item)

        for procurement, rule in procurements:
            if not rule.location_src_id:
                msg = _('No source location defined on stock rule: %s!') % (rule.name,)
                raise ProcurementException([(procurement, msg)])

            if rule.procure_method == 'mts_else_mto':
                mtso_products_by_locations[rule.location_src_id].append(procurement.product_id.id)

        # Get the forecasted quantity for the `mts_else_mto` procurement.
        forecasted_qties_by_loc = {}
        for location, product_ids in mtso_products_by_locations.items():
            products = self.env['product.product'].browse(product_ids).with_context(location=location.id)
            forecasted_qties_by_loc[location] = {product.id: product.free_qty for product in products}

        # Prepare the move values, adapt the `procure_method` if needed.
        procurements = sorted(procurements, key=lambda proc: float_compare(proc[0].product_qty, 0.0,
                                                                           precision_rounding=proc[
                                                                               0].product_uom.rounding) > 0)
        list2 = []
        for item in procurements:
            num = int(item[0].product_qty)

            product = self.env['product.product'].search(
                [("id", '=', item[0].product_id.id)])
            product_tmpl = self.env['product.template'].search(
                ["&", ("id", '=', product.product_tmpl_id.id), ('single_manufacturing', "!=", False)])
            if product_tmpl:
                if num > 1:
                    for no in range(1, num + 1):
                        Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
                                                                 'product_uom', 'location_id', 'name', 'origin',
                                                                 'company_id',
                                                                 'values'])
                        s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom,
                                        location_id=item[0].location_id,
                                        name=item[0].name,
                                        origin=item[0].origin,
                                        company_id=item[0].company_id,
                                        values=item[0].values,
                                        )
                        item1 = list(item)
                        item1[0] = s

                        list2.append(tuple(item1))
                else:
                    list2.append(item)
            else:
                list2.append(item)

        for procurement, rule in list2:
            procure_method = rule.procure_method
            if rule.procure_method == 'mts_else_mto':
                qty_needed = procurement.product_uom._compute_quantity(procurement.product_qty,
                                                                       procurement.product_id.uom_id)
                if float_compare(qty_needed, 0, precision_rounding=procurement.product_id.uom_id.rounding) <= 0:
                    procure_method = 'make_to_order'
                    for move in procurement.values.get('group_id', self.env['procurement.group']).stock_move_ids:
                        if move.rule_id == rule and float_compare(move.product_uom_qty, 0,
                                                                  precision_rounding=move.product_uom.rounding) > 0:
                            procure_method = move.procure_method
                            break
                    forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
                elif float_compare(qty_needed, forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id],
                                   precision_rounding=procurement.product_id.uom_id.rounding) > 0:
                    procure_method = 'make_to_order'
                else:
                    forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
                    procure_method = 'make_to_stock'

            move_values = rule._get_stock_move_values(*procurement)
            move_values['procure_method'] = procure_method
            moves_values_by_company[procurement.company_id.id].append(move_values)

        for company_id, moves_values in moves_values_by_company.items():
            # create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
            moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(moves_values)
            # Since action_confirm launch following procurement_group we should activate it.
            moves._action_confirm()

        return True

    @api.model
    def _run_manufacture(self, procurements):
        productions_values_by_company = defaultdict(list)
        errors = []
        for procurement, rule in procurements:
            if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0:
                # If procurement contains negative quantity, don't create a MO that would be for a negative value.
                continue
            bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values)

            productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom))

        if errors:
            raise ProcurementException(errors)

        for company_id, productions_values in productions_values_by_company.items():
            # create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
            '''创建制造订单'''
            productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
                productions_values)
            self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
            self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
            '''
            创建工单
            '''
            productions._create_workorder()

            productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
                                           (
                                                   p.move_dest_ids.procure_method != 'make_to_order' and not p.move_raw_ids and not p.workorder_ids)).action_confirm()

            for production in productions:
                '''
                创建制造订单时生成序列号
                '''
                production.action_generate_serial()
                origin_production = production.move_dest_ids and production.move_dest_ids[
                    0].raw_material_production_id or False
                orderpoint = production.orderpoint_id
                if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
                    production.message_post(
                        body=_('This production order has been created from Replenishment Report.'),
                        message_type='comment',
                        subtype_xmlid='mail.mt_note')
                elif orderpoint:
                    production.message_post_with_view(
                        'mail.message_origin_link',
                        values={'self': production, 'origin': orderpoint},
                        subtype_id=self.env.ref('mail.mt_note').id)
                elif origin_production:
                    production.message_post_with_view(
                        'mail.message_origin_link',
                        values={'self': production, 'origin': origin_production},
                        subtype_id=self.env.ref('mail.mt_note').id)
        return True


class ProductionLot(models.Model):
    _inherit = 'stock.lot'

    @api.model
    def generate_lot_names1(self, display_name, first_lot, count):
        """Generate `lot_names` from a string."""
        if first_lot.__contains__(display_name):
            first_lot = first_lot[(len(display_name) + 1):]

        # We look if the first lot contains at least one digit.
        caught_initial_number = regex_findall(r"\d+", first_lot)
        if not caught_initial_number:
            return self.generate_lot_names1(display_name, first_lot + "0", count)
        # We base the series on the last number found in the base lot.
        initial_number = caught_initial_number[-1]
        padding = len(initial_number)
        # We split the lot name to get the prefix and suffix.
        splitted = regex_split(initial_number, first_lot)
        # initial_number could appear several times, e.g. BAV023B00001S00001
        prefix = initial_number.join(splitted[:-1])
        suffix = splitted[-1]
        initial_number = int(initial_number)

        lot_names = []
        for i in range(0, count):
            lot_names.append('%s-%s%s%s' % (
                display_name,
                prefix,
                str(initial_number + i).zfill(padding),
                suffix
            ))
        return lot_names

    @api.model
    def _get_next_serial(self, company, product):
        """Return the next serial number to be attributed to the product."""
        if product.tracking == "serial":
            last_serial = self.env['stock.lot'].search(
                [('company_id', '=', company.id), ('product_id', '=', product.id)],
                limit=1, order='id DESC')
            if last_serial:
                return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name, 2)[
                    1]
        return "%s-%03d" % (product.name, 1)
