# Copyright 2020 Tecnativa - Carlos Dauden
# Copyright 2020 Tecnativa - Sergio Teruel
# Copyright 2023 Tecnativa - Carolina Fernandez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from freezegun import freeze_time

from odoo.exceptions import UserError, ValidationError
from odoo.tests import Form, TransactionCase, tagged


@tagged("post_install", "-at_install")
class TestSaleInvoicePayment(TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        if not cls.env.company.chart_template_id:
            # Load a CoA if there's none in current company
            coa = cls.env.ref("l10n_generic_coa.configurable_chart_template", False)
            if not coa:
                # Load the first available CoA
                coa = cls.env["account.chart.template"].search(
                    [("visible", "=", True)], limit=1
                )
            coa.try_loading(company=cls.env.company, install_demo=False)
        # Remove time zone from user to avoid to time local representation
        cls.env.user.partner_id.tz = False
        # Archive all reconciliation models to avoid them interfering with the tests
        cls.env["account.reconcile.model"].search([]).active = False
        account_group = cls.env.ref("account.group_account_user")
        cls.env.user.write({"groups_id": [(4, account_group.id)]})
        no_one_group = cls.env.ref("base.group_no_one")
        cls.env.user.write({"groups_id": [(4, no_one_group.id)]})
        cls.wizard_obj = cls.env["sale.invoice.payment.wiz"]
        cls.SalePaymentSheet = cls.env["sale.payment.sheet"]
        cls.partner = cls.env["res.partner"].create({"name": "Test partner"})
        suspense_account = cls.env["account.account"].create(
            {
                "code": "assetcurrent",
                "name": "Test Current Asset",
                "account_type": "asset_current",
            }
        )
        cls.bank_journal = cls.env["account.journal"].create(
            {
                "name": "Bank journal",
                "type": "bank",
                "code": "test",
                "suspense_account_id": suspense_account.id,
            }
        )
        cls.account_invoice = cls.env["account.account"].create(
            {
                "code": "test",
                "name": "Test account",
                "account_type": "income",
            }
        )
        cls.invoice1 = cls._create_invoice(cls)
        cls.invoice2 = cls._create_invoice(cls)
        cls.refund1 = cls._create_refund(cls)
        (cls.invoice1 + cls.invoice2 + cls.refund1).action_post()

    def _create_invoice(self):
        invoice_form = Form(
            self.env["account.move"].with_context(default_move_type="out_invoice")
        )
        invoice_form.partner_id = self.partner
        with invoice_form.invoice_line_ids.new() as line_form:
            line_form.name = "invoice test"
            line_form.account_id = self.account_invoice
            line_form.quantity = 1.0
            line_form.price_unit = 100.00
        return invoice_form.save()

    def _create_refund(self):
        invoice_form = Form(
            self.env["account.move"].with_context(default_move_type="out_refund")
        )
        invoice_form.partner_id = self.partner
        with invoice_form.invoice_line_ids.new() as line_form:
            line_form.name = "invoice test"
            line_form.account_id = self.account_invoice
            line_form.quantity = 1.0
            line_form.price_unit = 10.00
        return invoice_form.save()

    def test_payment_wizard(self):
        PaymentWiz = self.env["sale.invoice.payment.wiz"].with_context(
            active_model="account.move",
            active_ids=(self.invoice1 + self.invoice2).ids,
        )
        with Form(PaymentWiz) as wiz_form:
            wiz_form.journal_id = self.bank_journal
            wiz_form.amount = 150.00
        wiz = wiz_form.save()
        action = wiz.create_sale_invoice_payment_sheet()
        sheet = self.SalePaymentSheet.browse(action["res_id"])
        self.assertEqual(len(sheet.line_ids), 2)
        line_partial_payment = sheet.line_ids.filtered(
            lambda ln: ln.transaction_type == "partial"
        )
        self.assertTrue(line_partial_payment)
        self.assertEqual(line_partial_payment.invoice_id, self.invoice2)
        line_full_payment = sheet.line_ids.filtered(
            lambda ln: ln.transaction_type == "full"
        )
        self.assertTrue(line_full_payment)
        self.assertEqual(line_full_payment.invoice_id, self.invoice1)
        self.assertEqual(sheet.amount_total, 150.00)

    def _create_payment_sheet(self):
        with Form(self.SalePaymentSheet) as sheet_form:
            sheet_form.journal_id = self.bank_journal
            for index, invoice in enumerate(self.invoice1 + self.invoice2):
                with sheet_form.line_ids.new() as line_sheet:
                    line_sheet.partner_id = self.partner
                    line_sheet.invoice_id = invoice
                    # Only write for partial amount payed, by default the
                    # amount line is total amount residual
                    if index > 0:
                        line_sheet.amount = 50.0
        return sheet_form.save()

    @freeze_time("2021-01-01 09:30:00")
    def test_manual_payment_sheet(self):
        sheet = self._create_payment_sheet()
        self.assertEqual(len(sheet.line_ids), 2)
        line_partial_payment = sheet.line_ids.filtered(
            lambda ln: ln.transaction_type == "partial"
        )
        self.assertTrue(line_partial_payment)
        self.assertEqual(line_partial_payment.invoice_id, self.invoice2)
        line_full_payment = sheet.line_ids.filtered(
            lambda ln: ln.transaction_type == "full"
        )
        self.assertTrue(line_full_payment)
        self.assertEqual(line_full_payment.invoice_id, self.invoice1)
        self.assertEqual(
            sheet.name,
            "{} - {} - {}".format(
                sheet.date.strftime("%Y.%m.%d"),
                sheet.journal_id.name,
                sheet.user_id.name,
            ),
        )
        self.assertEqual(
            line_partial_payment.name,
            "[{}] - {} - {} - ({})".format(
                "09:30",
                line_partial_payment.sheet_id.user_id.name,
                line_partial_payment.invoice_id.name,
                dict(
                    line_partial_payment._fields[
                        "transaction_type"
                    ]._description_selection(line_partial_payment.env)
                ).get(line_partial_payment.transaction_type),
            ),
        )

    def test_payment_sheet_confirm(self):
        sheet = self._create_payment_sheet()
        sheet.button_confirm_sheet()
        self.assertTrue(sheet.statement_id)
        self.assertEqual(len(sheet.line_ids.mapped("statement_line_id")), 2)

    def test_payment_sheet_reopen(self):
        sheet = self._create_payment_sheet()
        sheet.button_confirm_sheet()
        sheet.button_reopen()
        self.assertFalse(sheet.statement_id)

    def test_payment_sheet_unlink(self):
        sheet = self._create_payment_sheet()
        sheet.button_confirm_sheet()
        with self.assertRaises(UserError):
            sheet.unlink()

    def test_payment_sheet_line_unlink(self):
        sheet = self._create_payment_sheet()
        sheet.button_confirm_sheet()
        with self.assertRaises(UserError):
            sheet.line_ids.unlink()

    def test_button_bank_statement(self):
        sheet = self._create_payment_sheet()
        sheet.button_bank_statement()

    def test_payment_sheet_invoice_constraint(self):
        # You can not add full invoice payed more than one time.
        sheet = self._create_payment_sheet()
        with self.assertRaises(ValidationError):
            with Form(sheet) as sheet_form:
                with sheet_form.line_ids.new() as line_sheet:
                    line_sheet.partner_id = self.partner
                    line_sheet.invoice_id = self.invoice1
            sheet_form.save()
