import re
from dateutil.relativedelta import relativedelta


from odoo import Command, fields
from odoo.tools.misc import clean_context
from odoo.tests import Form
from odoo.addons.base.tests.common import BaseCommon


class TestStockValuationCommon(BaseCommon):
    # Override
    @classmethod
    def _create_company(cls, **create_values):
        company = super()._create_company(**create_values)
        cls.env["account.chart.template"]._load(
            "generic_coa", company, install_demo=False
        )
        return company

    # HELPER
    def _create_account_move(self, move_type, product, quantity=1.0, price_unit=1.0, post=True, **kwargs):
        invoice_vals = {
            "partner_id": self.vendor.id,
            "move_type": move_type,
            "invoice_date": kwargs.get('invoice_date', fields.Date.today()),
            "invoice_line_ids": [],
        }
        if kwargs.get('reversed_entry_id'):
            invoice_vals["reversed_entry_id"] = kwargs['reversed_entry_id']
        invoice = self.env["account.move"].create(invoice_vals)
        product_uom = kwargs.get('product_uom') or product.uom_id
        self.env["account.move.line"].create({
            "move_id": invoice.id,
             "display_type": "product",
             "name": "test line",
             "price_unit": price_unit,
             "quantity": quantity,
             "product_id": product.id,
             "product_uom_id": product_uom.id,
             "tax_ids": [(5, 0, 0)],
        })
        if post:
            invoice.action_post()
        return invoice

    def _create_invoice(self, product=None, quantity=1.0, price_unit=None, post=True, **kwargs):
        return self._create_account_move("out_invoice", product, quantity, price_unit, post, **kwargs)

    def _create_bill(self, product=None, quantity=1.0, price_unit=None, post=True, **kwargs):
        return self._create_account_move("in_invoice", product, quantity, price_unit, post, **kwargs)

    def _create_credit_note(self, product, quantity=1.0, price_unit=1.0, post=True, **kwargs):
        move_type = kwargs.pop("move_type", "out_refund")
        return self._create_account_move(move_type, product, quantity, price_unit, post, **kwargs)

    def _refund(self, move_to_refund, quantity=None, post=True):
        reversal = self.env['account.move.reversal'].with_context(active_ids=move_to_refund.ids, active_model='account.move').create({
            'journal_id': move_to_refund.journal_id.id,
        })
        credit_note = self.env['account.move'].browse(reversal.refund_moves()['res_id'])
        if quantity:
            credit_note.line_ids.quantity = quantity
        if post:
            credit_note.action_post()
        return credit_note

    def _close(self, auto_post=True, at_date=None):
        action = self.company.action_close_stock_valuation(at_date=at_date, auto_post=auto_post)
        return action['res_id'] and self.env['account.move'].browse(action['res_id'])

    def _use_price_diff(self):
        self.account_price_diff = self.env['account.account'].create({
            'name': 'Price Difference Account',
            'code': '100102',
            'account_type': 'asset_current',
        })
        self.category_standard.property_price_difference_account_id = self.account_price_diff.id
        self.category_standard_auto.property_price_difference_account_id = self.account_price_diff.id
        return self.account_price_diff

    def _use_route_mto(self, product):
        if not self.route_mto.active:
            self.route_mto.active = True
        product.route_ids = [(4, self.route_mto.id)]
        return product

    def _use_multi_currencies(self, rates=None):
        date_1 = fields.Date.today()
        date_2 = date_1 + relativedelta(days=1)
        date_3 = date_2 + relativedelta(days=1)
        rates = rates or [
            (fields.Date.to_string(date_1), 1),
            (fields.Date.to_string(date_2), 2),
            (fields.Date.to_string(date_3), 3),
        ]
        self.other_currency = self.setup_other_currency('EUR', rates=rates)

    def _use_multi_warehouses(self):
        self.other_warehouse = self.env['stock.warehouse'].create({
            'name': 'Other Warehouse',
            'code': 'OWH',
            'company_id': self.company.id,
        })

    def _use_inventory_location_accounting(self):
        self.account_inventory = self.env['account.account'].create({
            'name': 'Inventory Account',
            'code': '100101',
            'account_type': 'asset_current',
        })
        inventory_locations = self.env['stock.location'].search([('usage', '=', 'inventory'), ('company_id', '=', self.company.id)])
        inventory_locations.valuation_account_id = self.account_inventory.id
        return self.account_inventory

    # Moves
    def _make_in_move(self,
            product,
            quantity,
            unit_cost=None,
            create_picking=False,
            company=None,
            **kwargs,
        ):
        """ Helper to create and validate a receipt move.

        :param product: Product to move
        :param quantity: Quantity to move
        :param unit_cost: Price unit
        :param create_picking: Create the picking containing the created move
        :param company: If set, the move is created in that company's context
            and warehouse defaults are resolved from that company's warehouse.
        :param **kwargs: stock.move fields that you can override
            ''location_id: origin location for the move
            ''location_dest_id: destination location for the move
            ''lot_ids: list of lot (split among the quantity)
            ''picking_type_id: picking type
            ''uom_id: Unit of measure
            ''owner_id: Consignment owner
        """
        env = self.env['stock.move'].with_company(company).env if company else self.env
        if company:
            warehouse = env['stock.warehouse'].search([('company_id', '=', company.id)], limit=1)
            default_dest = warehouse.lot_stock_id.id
            default_picking_type = warehouse.in_type_id.id
        else:
            default_dest = self.stock_location.id
            default_picking_type = self.picking_type_in.id

        product_qty = quantity
        if kwargs.get('uom_id'):
            uom = self.env['uom.uom'].browse(kwargs.get('uom_id'))
            product_qty = uom._compute_quantity(quantity, product.uom_id)
        move_vals = {
            'product_id': product.id,
            'location_id': kwargs.get('location_id', self.supplier_location.id),
            'location_dest_id': kwargs.get('location_dest_id', default_dest),
            'product_uom': kwargs.get('uom_id', self.uom.id),
            'product_uom_qty': quantity,
            'picking_type_id': kwargs.get('picking_type_id', default_picking_type),
        }
        if unit_cost:
            move_vals['value_manual'] = unit_cost * product_qty
            move_vals['price_unit'] = unit_cost
        else:
            move_vals['value_manual'] = product.standard_price * product_qty
        in_move = env['stock.move'].create(move_vals)

        if create_picking:
            picking = env['stock.picking'].create({
                'picking_type_id': in_move.picking_type_id.id,
                'location_id': in_move.location_id.id,
                'location_dest_id': in_move.location_dest_id.id,
                'owner_id': kwargs.get('owner_id', False),
                'partner_id': kwargs.get('partner_id', False),
                })
            in_move.picking_id = picking.id

        in_move._action_confirm()
        lot_ids = kwargs.get('lot_ids')
        if lot_ids:
            in_move.move_line_ids.unlink()
            in_move.move_line_ids = [Command.create({
                'location_id': self.supplier_location.id,
                'location_dest_id': in_move.location_dest_id.id,
                'quantity': quantity / len(lot_ids),
                'product_id': product.id,
                'lot_id': lot.id,
            }) for lot in lot_ids]
        else:
            in_move._action_assign()

        if not create_picking and kwargs.get('owner_id'):
            in_move.move_line_ids.owner_id = kwargs.get('owner_id')

        in_move.picked = True
        if create_picking:
            picking.button_validate()
        else:
            in_move._action_done()

        return in_move

    def _make_out_move(self,
        product,
        quantity,
        force_assign=True,
        create_picking=False,
        company=None,
        **kwargs,
    ):
        """ Helper to create and validate a delivery move.

        :param product: Product to move
        :param quantity: Quantity to move
        :param force_assign: Bypass reservation to force the required quantity
        :param create_picking: Create the picking containing the created move
        :param company: If set, the move is created in that company's context
            and warehouse defaults are resolved from that company's warehouse.
        :param **kwargs: stock.move fields that you can override
            ''location_id: origin location for the move
            ''location_dest_id: destination location for the move
            ''lot_ids: list of lot (split among the quantity)
            ''picking_type_id: picking type
            ''uom_id: Unit of measure
            ''owner_id: Consignment owner
        """
        env = self.env['stock.move'].with_company(company).env if company else self.env
        if company:
            warehouse = env['stock.warehouse'].search([('company_id', '=', company.id)], limit=1)
            default_src = warehouse.lot_stock_id.id
            default_picking_type = warehouse.out_type_id.id
        else:
            default_src = self.stock_location.id
            default_picking_type = self.picking_type_out.id

        out_move = env['stock.move'].create({
            'product_id': product.id,
            'location_id': kwargs.get('location_id', default_src),
            'location_dest_id': kwargs.get('location_dest_id', self.customer_location.id),
            'product_uom': kwargs.get('uom_id', self.uom.id),
            'product_uom_qty': quantity,
            'picking_type_id': kwargs.get('picking_type_id', default_picking_type),
        })

        if create_picking:
            picking = env['stock.picking'].create({
                'picking_type_id': out_move.picking_type_id.id,
                'location_id': out_move.location_id.id,
                'location_dest_id': out_move.location_dest_id.id,
                })
            out_move.picking_id = picking.id

        out_move._action_confirm()
        out_move._action_assign()
        lot_ids = kwargs.get('lot_ids')
        if lot_ids:
            out_move.move_line_ids.unlink()
            out_move.move_line_ids = [Command.create({
                'location_id': out_move.location_id.id,
                'location_dest_id': self.customer_location.id,
                'quantity': quantity / len(lot_ids),
                'product_id': product.id,
                'lot_id': lot.id,
            }) for lot in lot_ids]
        elif force_assign:
            out_move.quantity = quantity
        out_move.picked = True
        out_move._action_done()

        return out_move

    def _make_dropship_move(self,
        product,
        quantity,
        unit_cost=None,
        create_picking=False,
        company=None,
        **kwargs,
    ):
        kwargs.setdefault('location_dest_id', self.customer_location.id)
        return self._make_in_move(product, quantity, unit_cost, create_picking, company, **kwargs)

    def _make_return(self, move, quantity_to_return):
        stock_return_picking = Form(self.env['stock.return.picking']
            .with_context(active_ids=[move.picking_id.id], active_id=move.picking_id.id, active_model='stock.picking'))
        stock_return_picking = stock_return_picking.save()
        stock_return_picking.product_return_moves.quantity = quantity_to_return
        stock_return_picking_action = stock_return_picking.action_create_returns()
        return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
        return_pick.move_ids[0].quantity = quantity_to_return
        return_pick.move_ids[0].picked = True
        return_pick._action_done()
        return return_pick.move_ids

    # Post move processing
    def _add_move_line(self, move, **kwargs):
        old_price_unit = move._get_price_unit()
        self.env['stock.move.line'].create({
            'move_id': move.id,
            'product_id': move.product_id.id,
            'product_uom_id': move.product_uom.id,
            'location_id': move.location_id.id,
            'location_dest_id': move.location_dest_id.id,
        } | kwargs)
        move.value_manual = old_price_unit * move.quantity

    def _set_quantity(self, move, quantity):
        """Helper function to retroactively change the quantity of a move.
           The total value of the product will be recomputed as a result,
           regardless of the valuation method."""
        price_unit = move._get_price_unit()
        move.quantity = quantity
        move.value_manual = price_unit * quantity

    # GETTER
    def _get_stock_valuation_move_lines(self):
        return self.env['account.move.line'].search([
            ('account_id', '=', self.account_stock_valuation.id),
        ], order='date, id')

    def _get_stock_variation_move_lines(self):
        return self.env['account.move.line'].search([
            ('account_id', '=', self.account_stock_variation.id),
        ], order='date, id')

    def _get_expense_move_lines(self):
        return self.env['account.move.line'].search([
            ('account_id', '=', self.account_expense.id),
        ], order='date, id')

    def _url_extract_rec_id_and_model(self, url):
        # Extract model and record ID
        action_match = re.findall(r'action-([^/]+)', url)
        model_name = self.env.ref(action_match[0]).res_model
        rec_id = re.findall(r'/(\d+)$', url)[0]
        return rec_id, model_name

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        # To move to stock common later
        cls.route_mto = cls.env.ref('stock.route_warehouse0_mto')
        cls.company = cls.env['res.company'].create({'name': 'Inventory Test Company'})
        cls.env["account.chart.template"]._load(
            "generic_coa", cls.company, install_demo=False
        )
        cls.company = cls.company.with_company(cls.company.id)
        cls.env = cls.company.env
        cls.env.invalidate_all()
        # We use the admin on tour.
        cls.user_admin = cls.env.ref('base.user_admin')
        cls.user_admin.write({
            'company_id': cls.company.id,
            'company_ids': cls.company.ids,
        })
        cls.inventory_user = cls._create_new_internal_user(name='Inventory User', login='inventory_user', groups='stock.group_stock_user')
        cls.owner = cls._create_partner(name='Consignment Owner')
        cls.warehouse = cls.env['stock.warehouse'].search([('company_id', '=', cls.company.id)], limit=1)
        cls.stock_location = cls.warehouse.lot_stock_id
        cls.customer_location = cls.env.ref('stock.stock_location_customers')
        cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
        cls.inventory_location = cls.env['stock.location'].search([
            ('usage', '=', 'inventory'),
            ('company_id', '=', cls.company.id)
        ], limit=1)

        cls.picking_type_in = cls.warehouse.in_type_id
        cls.picking_type_out = cls.warehouse.out_type_id
        cls.uom = cls.env.ref('uom.product_uom_unit')
        cls.uom_pack_of_6 = cls.env['uom.uom'].create({
            'name': 'Pack of 6',
            'relative_uom_id': cls.uom.id,
            'relative_factor': 6.0,
        })

        cls.vendor = cls.env['res.partner'].create({
            'name': 'Test Vendor',
            'company_id': cls.company.id,
        })
        cls.other_company = cls._create_company(name="Other Company")
        cls.branch = cls._create_company(name="Branch Company", parent_id=cls.company.id)

        # Stock account
        cls.account_expense = cls.company.expense_account_id
        cls.account_stock_valuation = cls.company.account_stock_valuation_id
        cls.account_stock_variation = cls.account_stock_valuation.account_stock_variation_id
        cls.account_payable = cls.company.partner_id.property_account_payable_id
        cls.account_receivable = cls.company.partner_id.property_account_receivable_id
        cls.account_income = cls.company.income_account_id

        cls.category_standard = cls.env['product.category'].create({
            'name': 'Standard',
            'property_valuation': 'periodic',
            'property_cost_method': 'standard',
        })
        cls.category_standard_auto = cls.category_standard.copy({
            'name': 'Standard Auto',
            'property_valuation': 'real_time',
        })
        cls.category_fifo = cls.env['product.category'].create({
            'name': 'Fifo',
            'property_valuation': 'periodic',
            'property_cost_method': 'fifo',
        })
        cls.category_fifo_auto = cls.category_fifo.copy({
            'name': 'Fifo Auto',
            'property_valuation': 'real_time',
        })
        cls.category_avco = cls.env['product.category'].create({
            'name': 'Avco',
            'property_valuation': 'periodic',
            'property_cost_method': 'average',
        })
        cls.category_avco_auto = cls.category_avco.copy({
            'name': 'Avco Auto',
            'property_valuation': 'real_time',
        })

        cls.product_common_vals = {
            "standard_price": 10.0,
            "list_price": 20.0,
            "uom_id": cls.uom.id,
            "is_storable": True,
        }
        cls.product = cls.env['product.product'].create(
            {**cls.product_common_vals, 'name': 'Storable Product'}).with_context(clean_context(cls.env.context))
        cls.product_standard = cls.env['product.product'].create({
            **cls.product_common_vals,
            'name': 'Standard Product',
            'categ_id': cls.category_standard.id,
        }).with_context(clean_context(cls.env.context))
        cls.product_standard_auto = cls.env['product.product'].create({
            **cls.product_common_vals,
            'name': 'Standard Product Auto',
            'categ_id': cls.category_standard_auto.id,
        }).with_context(clean_context(cls.env.context))
        cls.product_fifo = cls.env['product.product'].create({
            **cls.product_common_vals,
            'name': 'Fifo Product',
            'categ_id': cls.category_fifo.id,
        }).with_context(clean_context(cls.env.context))
        cls.product_fifo_auto = cls.env['product.product'].create({
            **cls.product_common_vals,
            'name': 'Fifo Product Auto',
            'categ_id': cls.category_fifo_auto.id,
        }).with_context(clean_context(cls.env.context))
        cls.product_avco = cls.env['product.product'].create({
            **cls.product_common_vals,
            'name': 'Avco Product',
            'categ_id': cls.category_avco.id,
        }).with_context(clean_context(cls.env.context))
        cls.product_avco_auto = cls.env['product.product'].create({
            **cls.product_common_vals,
            'name': 'Avco Product Auto',
            'categ_id': cls.category_avco_auto.id,
        }).with_context(clean_context(cls.env.context))
