# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from freezegun import freeze_time

from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
from odoo.tests.common import Form, tagged
from odoo import Command, fields



@tagged('post_install', '-at_install')
class TestValuationReconciliation(ValuationReconciliationTestCommon):
    @classmethod
    def setup_company_data(cls, company_name, chart_template=None, **kwargs):
        company_data = super().setup_company_data(company_name, chart_template=chart_template, **kwargs)

        # Create stock config.
        company_data.update({
            'default_account_stock_price_diff': cls.env['account.account'].create({
                'name': 'default_account_stock_price_diff',
                'code': 'STOCKDIFF',
                'reconcile': True,
                'account_type': 'asset_current',
                'company_id': company_data['company'].id,
            }),
        })
        return company_data

    def _create_purchase(self, product, date, quantity=1.0, set_tax=False, price_unit=66.0):
        with freeze_time(date):
            rslt = self.env['purchase.order'].create({
                'partner_id': self.partner_a.id,
                'currency_id': self.currency_data['currency'].id,
                'order_line': [
                    (0, 0, {
                        'name': product.name,
                        'product_id': product.id,
                        'product_qty': quantity,
                        'product_uom': product.uom_po_id.id,
                        'price_unit': price_unit,
                        'date_planned': date,
                        'taxes_id': [(6, 0, product.supplier_taxes_id.ids)] if set_tax else False,
                    })],
                'date_order': date,
            })
            rslt.button_confirm()
            return rslt

    def _create_invoice_for_po(self, purchase_order, date):
        with freeze_time(date):
            move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice', default_date=date))
            move_form.invoice_date = date
            move_form.partner_id = self.partner_a
            move_form.currency_id = self.currency_data['currency']
            move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-purchase_order.id)
            return move_form.save()

    def test_shipment_invoice(self):
        """ Tests the case into which we receive the goods first, and then make the invoice.
        """
        test_product = self.test_product_delivery
        date_po_and_delivery = '2018-01-01'

        purchase_order = self._create_purchase(test_product, date_po_and_delivery)
        self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)

        invoice = self._create_invoice_for_po(purchase_order, '2018-02-02')
        invoice.action_post()
        picking = self.env['stock.picking'].search([('purchase_id','=',purchase_order.id)])
        self.check_reconciliation(invoice, picking)
        # cancel the invoice
        invoice.button_cancel()

    def test_invoice_shipment(self):
        """ Tests the case into which we make the invoice first, and then receive the goods.
        """
        # Create a PO and an invoice for it
        test_product = self.test_product_order
        purchase_order = self._create_purchase(test_product, '2017-12-01')

        invoice = self._create_invoice_for_po(purchase_order, '2017-12-23')
        move_form = Form(invoice)
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.quantity = 1
        invoice = move_form.save()

        # Validate the invoice and refund the goods
        invoice.action_post()
        self._process_pickings(purchase_order.picking_ids, date='2017-12-24')
        picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)])
        self.check_reconciliation(invoice, picking)

        # Return the goods and refund the invoice
        with freeze_time('2018-01-13'):
            stock_return_picking_form = Form(self.env['stock.return.picking'].with_context(
                active_ids=picking.ids, active_id=picking.ids[0], active_model='stock.picking'))
            stock_return_picking = stock_return_picking_form.save()
            stock_return_picking.product_return_moves.quantity = 1.0
            stock_return_picking_action = stock_return_picking.create_returns()
            return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
            return_pick.action_assign()
            return_pick.move_ids.quantity = 1
            return_pick.move_ids.picked = True
            return_pick._action_done()

        # Refund the invoice
        refund_invoice_wiz = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=[invoice.id]).create({
            'reason': 'test_invoice_shipment_refund',
            'date': '2018-03-15',
            'journal_id': invoice.journal_id.id,
        })
        new_invoice = self.env['account.move'].browse(refund_invoice_wiz.modify_moves()['res_id'])
        refund_invoice = invoice.reversal_move_id
        # Check the result
        self.assertEqual(invoice.payment_state, 'reversed', "Invoice should be in 'reversed' state")
        self.assertEqual(refund_invoice.payment_state, 'paid', "Refund should be in 'paid' state")
        self.assertEqual(new_invoice.state, 'draft', "New invoice should be in 'draft' state")
        self.check_reconciliation(refund_invoice, return_pick)

    def test_multiple_shipments_invoices(self):
        """ Tests the case into which we receive part of the goods first, then 2 invoices at different rates, and finally the remaining quantities
        """
        test_product = self.test_product_delivery
        date_po_and_delivery0 = '2017-01-01'
        purchase_order = self._create_purchase(test_product, date_po_and_delivery0, quantity=5.0)
        self._process_pickings(purchase_order.picking_ids, quantity=2.0, date=date_po_and_delivery0)
        picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order="id asc", limit=1)

        invoice = self._create_invoice_for_po(purchase_order, '2017-01-15')
        move_form = Form(invoice)
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.quantity = 3.0
        invoice = move_form.save()
        invoice.action_post()
        self.check_reconciliation(invoice, picking, full_reconcile=False)

        invoice2 = self._create_invoice_for_po(purchase_order, '2017-02-15')
        move_form = Form(invoice2)
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.quantity = 2.0
        invoice2 = move_form.save()
        invoice2.action_post()
        self.check_reconciliation(invoice2, picking, full_reconcile=False)

        # We don't need to make the date of processing explicit since the very last rate
        # will be taken
        self._process_pickings(purchase_order.picking_ids.filtered(lambda x: x.state != 'done'), quantity=3.0)
        picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order='id desc', limit=1)
        self.check_reconciliation(invoice2, picking)

    def test_rounding_discount(self):
        self.env.ref("product.decimal_discount").digits = 5
        tax_exclude_id = self.env["account.tax"].create(
            {
                "name": "Exclude tax",
                "amount": "0.00",
                "type_tax_use": "purchase",
            }
        )

        test_product = self.test_product_delivery
        test_product.supplier_taxes_id = [(6, 0, tax_exclude_id.ids)]
        date_po_and_delivery = '2018-01-01'

        purchase_order = self._create_purchase(test_product, date_po_and_delivery, quantity=10000, set_tax=True)
        self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)

        invoice = self._create_invoice_for_po(purchase_order, '2018-01-01')

        # Set a discount
        move_form = Form(invoice)
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.discount = 0.92431
        move_form.save()
        invoice.action_post()

        # Check the price difference amount.
        invoice_layer = self.env['stock.valuation.layer'].search([('account_move_line_id', 'in', invoice.line_ids.ids)])
        self.assertTrue(len(invoice_layer) == 1, "A price difference line should be created")
        self.assertAlmostEqual(invoice_layer.value, -3050.22)

        picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)])
        self.assertAlmostEqual(invoice_layer.value + picking.move_ids.stock_valuation_layer_ids.value, invoice.line_ids[0].debit)
        self.assertAlmostEqual(invoice_layer.value + picking.move_ids.stock_valuation_layer_ids.value, invoice.invoice_line_ids.price_subtotal/2, 2)
        self.check_reconciliation(invoice, picking)

    def test_rounding_price_unit(self):
        self.env.ref("product.decimal_price").digits = 6

        test_product = self.test_product_delivery
        date_po_and_delivery = '2018-01-01'

        purchase_order = self._create_purchase(test_product, date_po_and_delivery, quantity=1000000, price_unit=0.0005)
        self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)

        invoice = self._create_invoice_for_po(purchase_order, '2018-01-01')

        # Set a discount
        move_form = Form(invoice)
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.price_unit = 0.0006
        move_form.save()
        invoice.action_post()

        # Check the price difference amount. It's expected that price_unit * qty != price_total.
        invoice_layer = self.env['stock.valuation.layer'].search([('account_move_line_id', 'in', invoice.line_ids.ids)])
        self.assertTrue(len(invoice_layer) == 1, "A price difference line should be created")
        # self.assertAlmostEqual(invoice_layer.price_unit, 0.0001)
        self.assertAlmostEqual(invoice_layer.value, 50.0)

        picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)])
        self.check_reconciliation(invoice, picking)

    @freeze_time('2021-01-03')
    def test_price_difference_exchange_difference_accounting_date(self):
        self.stock_account_product_categ.property_account_creditor_price_difference_categ = self.company_data['default_account_stock_price_diff']
        test_product = self.test_product_delivery
        test_product.categ_id.write({"property_cost_method": "standard"})
        test_product.write({'standard_price': 100.0})
        date_po_receipt = '2021-01-02'
        rate_po_receipt = 25.0
        date_bill = '2021-01-01'
        rate_bill = 30.0
        date_accounting = '2021-01-03'
        rate_accounting = 26.0

        foreign_currency = self.currency_data['currency']
        company_currency = self.env.company.currency_id
        self.env['res.currency.rate'].create([
        {
            'name': date_po_receipt,
            'rate': rate_po_receipt,
            'currency_id': foreign_currency.id,
            'company_id': self.env.company.id,
        }, {
            'name': date_bill,
            'rate': rate_bill,
            'currency_id': foreign_currency.id,
            'company_id': self.env.company.id,
        }, {
            'name': date_accounting,
            'rate': rate_accounting,
            'currency_id': foreign_currency.id,
            'company_id': self.env.company.id,
        }, {
            'name': date_po_receipt,
            'rate': 1.0,
            'currency_id': company_currency.id,
            'company_id': self.env.company.id,
        }, {
            'name': date_accounting,
            'rate': 1.0,
            'currency_id': company_currency.id,
            'company_id': self.env.company.id,
        }, {
            'name': date_bill,
            'rate': 1.0,
            'currency_id': company_currency.id,
            'company_id': self.env.company.id,
        }])

        #purchase order created in foreign currency
        purchase_order = self._create_purchase(test_product, date_po_receipt, quantity=10, price_unit=3000)
        with freeze_time(date_po_receipt):
            self._process_pickings(purchase_order.picking_ids)
        invoice = self._create_invoice_for_po(purchase_order, date_bill)
        with Form(invoice) as move_form:
            move_form.invoice_date = fields.Date.from_string(date_bill)
            move_form.date = fields.Date.from_string(date_accounting)
        invoice.action_post()

        price_diff_line = invoice.line_ids.filtered(lambda l: l.account_id == self.stock_account_product_categ.property_account_creditor_price_difference_categ)
        self.assertTrue(len(price_diff_line) == 1, "A price difference line should be created")
        self.assertAlmostEqual(price_diff_line.balance, 192.31)
        self.assertAlmostEqual(price_diff_line.price_subtotal, 5000.0)

        picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)])
        interim_account_id = self.company_data['default_account_stock_in'].id

        valuation_line = picking.move_ids.mapped('account_move_ids.line_ids').filtered(lambda x: x.account_id.id == interim_account_id)
        self.assertTrue(valuation_line.full_reconcile_id, "The reconciliation should be total at that point.")

    def test_reconcile_cash_basis_bill(self):
        ''' Test the generation of the CABA move after bill payment
        '''
        self.env.company.tax_exigibility = True
        cash_basis_base_account = self.env['account.account'].create({
            'code': 'cash.basis.base.account',
            'name': 'cash_basis_base_account',
            'account_type': 'income',
            'company_id': self.company_data['company'].id,
        })
        self.company_data['company'].account_cash_basis_base_account_id = cash_basis_base_account

        cash_basis_transfer_account = self.env['account.account'].create({
            'code': 'cash.basis.transfer.account',
            'name': 'cash_basis_transfer_account',
            'account_type': 'income',
            'company_id': self.company_data['company'].id,
        })

        tax_account_1 = self.env['account.account'].create({
            'code': 'tax.account.1',
            'name': 'tax_account_1',
            'account_type': 'income',
            'company_id': self.company_data['company'].id,
        })

        tax_tags = self.env['account.account.tag'].create({
            'name': 'tax_tag_%s' % str(i),
            'applicability': 'taxes',
        } for i in range(8))

        cash_basis_tax_a_third_amount = self.env['account.tax'].create({
            'name': 'tax_1',
            'amount': 33.3333,
            'company_id': self.company_data['company'].id,
            'cash_basis_transition_account_id': cash_basis_transfer_account.id,
            'type_tax_use': 'purchase',
            'tax_exigibility': 'on_payment',
            'invoice_repartition_line_ids': [
                (0, 0, {
                    'repartition_type': 'base',
                    'tag_ids': [(6, 0, tax_tags[0].ids)],
                }),

                (0, 0, {
                    'repartition_type': 'tax',
                    'account_id': tax_account_1.id,
                    'tag_ids': [(6, 0, tax_tags[1].ids)],
                }),
            ],
            'refund_repartition_line_ids': [
                (0, 0, {
                    'repartition_type': 'base',
                    'tag_ids': [(6, 0, tax_tags[2].ids)],
                }),

                (0, 0, {
                    'repartition_type': 'tax',
                    'account_id': tax_account_1.id,
                    'tag_ids': [(6, 0, tax_tags[3].ids)],
                }),
            ],
        })

        product_A = self.env["product.product"].create(
            {
                "name": "Product A",
                "type": "product",
                "default_code": "prda",
                "categ_id": self.stock_account_product_categ.id,
                "taxes_id": [(5, 0, 0)],
                "supplier_taxes_id": [(6, 0, cash_basis_tax_a_third_amount.ids)],
                "lst_price": 100.0,
                "standard_price": 10.0,
                "property_account_income_id": self.company_data["default_account_revenue"].id,
                "property_account_expense_id": self.company_data["default_account_expense"].id,
            }
        )
        product_A.categ_id.write(
            {
                "property_valuation": "real_time",
                "property_cost_method": "standard",
            }
        )

        date_po_and_delivery = '2018-01-01'
        purchase_order = self._create_purchase(product_A, date_po_and_delivery, set_tax=True, price_unit=300.0)
        self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)

        bill = self._create_invoice_for_po(purchase_order, '2018-02-02')
        bill.action_post()

        # Register a payment creating the CABA journal entry on the fly and reconcile it with the tax line.
        self.env['account.payment.register']\
            .with_context(active_ids=bill.ids, active_model='account.move')\
            .create({})\
            ._create_payments()

        partial_rec = bill.mapped('line_ids.matched_debit_ids')
        caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)])

        # Tax values based on payment
        # Invoice amount 300
        self.assertRecordValues(caba_move.line_ids, [
            # pylint: disable=C0326
            # Base amount:
            {'debit': 0.0,    'credit': 150.0,      'amount_currency': -300.0,   'account_id': cash_basis_base_account.id},
            {'debit': 150.0,      'credit': 0.0,    'amount_currency': 300.0,  'account_id': cash_basis_base_account.id},
            # tax:
            {'debit': 0.0,     'credit': 50.0,      'amount_currency': -100.0,   'account_id': cash_basis_transfer_account.id},
            {'debit': 50.0,      'credit': 0.0,     'amount_currency': 100.0,  'account_id': tax_account_1.id},
        ])

    def test_reconciliation_differed_billing(self):
        """
        Test whether products received billed at different time will be correctly reconciled
        valuation: automated
        - create a rfq
        - receive products
        - create bill - set quantity of product A = 0 - save
        - create bill - confirm
        -> the reconciliation should not take into account the lines of the first bill
        """
        date_po_and_delivery = '2022-03-02'
        self.product_a.write({
            'categ_id': self.stock_account_product_categ,
            'detailed_type': 'product',
        })
        self.product_b.write({
            'categ_id': self.stock_account_product_categ,
            'detailed_type': 'product',
        })
        purchase_order = self.env['purchase.order'].create({
                'currency_id': self.currency_data['currency'].id,
                'order_line': [
                    Command.create({
                        'name': self.product_a.name,
                        'product_id': self.product_a.id,
                        'product_qty': 1,
                    }),
                    Command.create({
                        'name': self.product_b.name,
                        'product_id': self.product_b.id,
                        'product_qty': 1,
                    }),
                ],
                'partner_id': self.partner_a.id,
            })
        purchase_order.button_confirm()
        self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)

        bill_1 = self._create_invoice_for_po(purchase_order, date_po_and_delivery)
        move_form = Form(bill_1)
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.quantity = 0
        move_form.save()

        bill_2 = self._create_invoice_for_po(purchase_order, date=date_po_and_delivery)
        bill_2.action_post()
        aml = bill_2.line_ids.filtered(lambda line: line.display_type == "product")
        pol = purchase_order.order_line
        self.assertRecordValues(pol, [{'qty_invoiced': line.qty_received} for line in pol])
        self.assertRecordValues(aml, [{'reconciled': True} for line in aml])

    def test_create_fifo_vacuum_anglo_saxon_expense_entry(self):

        # create purchase
        self.product_a.write({
            'standard_price': 27.0,
            'categ_id': self.stock_account_product_categ,
            'detailed_type': 'product',
        })

        self.stock_account_product_categ['property_cost_method'] = 'average'

        #create purchase
        date_po_and_delivery = '2018-01-01'
        purchase_order = self._create_purchase(self.product_a, date_po_and_delivery, 1, price_unit=27)

        # proccess picking
        self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)

        # create return
        picking = purchase_order.picking_ids[0]
        stock_return_picking_form = Form(self.env['stock.return.picking']
            .with_context(active_ids=picking.ids, active_id=picking.ids[0],
            active_model='stock.picking'))
        stock_return_picking = stock_return_picking_form.save()
        stock_return_picking.product_return_moves.write({'quantity': 1000.0})
        stock_return_picking_action = stock_return_picking.create_returns()
        return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
        return_pick.move_line_ids.write({'quantity': 1000})
        return_pick.button_validate()

        # create vendor bill
        move_form = Form(self.env['account.move'].with_context(default_move_type='in_refund'))
        move_form._view['modifiers']['purchase_id']['invisible'] = False
        move_form.partner_id = purchase_order.partner_id
        move_form.invoice_date = date_po_and_delivery
        move_form.purchase_id = purchase_order
        with move_form.invoice_line_ids.edit(0) as line_form:
            line_form.quantity = 999.0
        invoice = move_form.save()
        invoice.action_post()

        # register payment
        self.env['account.payment.register']\
            .with_context(active_ids=invoice.ids, active_model='account.move')\
            .create({})\
            ._create_payments()

        # create another purchase
        purchase_order2 = self.env['purchase.order'].create({
                'partner_id': self.partner_a.id,
                'currency_id': self.env.company.currency_id.id,
                'order_line': [
                    (0, 0, {
                        'name': self.product_a.name,
                        'product_id': self.product_a.id,
                        'product_qty': 1,
                        'product_uom': self.product_a.uom_po_id.id,
                        'price_unit': 29,
                        'date_planned': date_po_and_delivery,
                    })],
                'date_order': date_po_and_delivery,
            })
        # confirm PO
        purchase_order2.button_confirm()
        # process pickings
        self._process_pickings(purchase_order2.picking_ids, date_po_and_delivery)

        picking2 = purchase_order2.picking_ids[0]
        self.assertEqual(picking2.state, 'done')
