import os
from contextlib import contextmanager, ExitStack
from unittest import SkipTest
from unittest.mock import patch

from odoo import Command
from odoo.addons.account_avatax.lib.avatax_client import AvataxClient
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests.common import TransactionCase
from .mocked_invoice_1_response import generate_response as generate_response_invoice_1
from .mocked_invoice_2_response import generate_response as generate_response_invoice_2
from .mocked_invoice_3_response import generate_response as generate_response_invoice_3

NOTHING = object()


class TestAvataxCommon(TransactionCase):
    @classmethod
    def setUpClass(cls):
        res = super().setUpClass()
        cls.env.company.avalara_api_id = os.getenv("AVALARA_LOGIN_ID") or "AVALARA_LOGIN_ID"
        cls.env.company.avalara_api_key = os.getenv("AVALARA_API_KEY") or "AVALARA_API_KEY"
        cls.env.company.avalara_environment = 'sandbox'
        cls.env.company.avalara_commit = True

        company = cls.env.user.company_id
        company.write({
            'street': "250 Executive Park Blvd",
            'city': "San Francisco",
            'state_id': cls.env.ref("base.state_us_5").id,
            'country_id': cls.env.ref("base.us").id,
            'zip': "94134",
        })
        company.partner_id.avalara_partner_code = os.getenv("AVALARA_COMPANY_CODE") or "DEFAULT"

        cls.fp_avatax = cls.env['account.fiscal.position'].create({
            'name': 'Avatax',
            'is_avatax': True,
        })

        cls.partner = cls.env["res.partner"].create({
            'name': "Sale Partner",
            'street': "2280 Market St",
            'city': "San Francisco",
            'state_id': cls.env.ref("base.state_us_5").id,
            'country_id': cls.env.ref("base.us").id,
            'zip': "94114",
            'avalara_partner_code': 'CUST123456',
            'property_account_position_id': cls.fp_avatax.id,
        })

        return res

    @classmethod
    @contextmanager
    def _client_patched(cls, create_transaction_details=None, **kwargs):
        if kwargs.get('create_transaction') is None and create_transaction_details is not None:
            def create_transaction(self, transaction, include=None):
                return {
                    'lines': [{
                        'lineNumber': line['number'],
                        'details': create_transaction_details,
                    } for line in transaction['lines']],
                    'summary': create_transaction_details,
                }

        if kwargs.get('uncommit_transaction') is None:
            def uncommit_transaction(self, companyCode, transactionCode, include=None):
                return {}

        def request(self, method, *args, **kwargs):
            assert False, "Request not authorized in mock"

        fnames = {fname for fname in dir(AvataxClient) if not fname.startswith('_')} - {
            'add_credentials',
        }
        methods = {**{fname: None for fname in fnames}, **kwargs, **locals()}
        with ExitStack() as stack:
            for _patch in [
                patch(f'{AvataxClient.__module__}.AvataxClient.{fname}', methods[fname])
                for fname in fnames
            ]:
                stack.enter_context(_patch)
            yield

    @classmethod
    @contextmanager
    def _capture_request(cls, return_value=NOTHING, return_func=NOTHING):
        class Capture:
            val = None

            def capture_request(self, method, *args, **kwargs):
                self.val = kwargs
                if return_value is NOTHING:
                    return return_func(method, *args, **kwargs)
                return return_value

        capture = Capture()
        with patch(f'{AvataxClient.__module__}.AvataxClient.request', capture.capture_request):
            yield capture

    @classmethod
    @contextmanager
    def _skip_no_credentials(cls):
        if not os.getenv("AVALARA_LOGIN_ID") or not os.getenv("AVALARA_API_KEY") or not os.getenv("AVALARA_COMPANY_CODE"):
            raise SkipTest("no Avalara credentials")
        yield


class TestAccountAvataxCommon(TestAvataxCommon, AccountTestInvoicingCommon):
    @classmethod
    def setUpClass(cls):
        res = super().setUpClass()
        cls.product = cls.env["product.product"].create({
            'name': "Product",
            'default_code': 'PROD1',
            'barcode': '123456789',
            'list_price': 15.00,
            'standard_price': 15.00,
            'supplier_taxes_id': None,
            'avatax_category_id': cls.env.ref('odex30_account_avatax.DC010000').id,
        })
        cls.product_user = cls.env["product.product"].create({
            'name': "Odoo User",
            'list_price': 35.00,
            'standard_price': 35.00,
            'supplier_taxes_id': None,
            'avatax_category_id': cls.env.ref('odex30_account_avatax.DC010000').id,
        })
        cls.product_user_discound = cls.env["product.product"].create({
            'name': "Odoo User Initial Discount",
            'list_price': -5.00,
            'standard_price': -5.00,
            'supplier_taxes_id': None,
            'avatax_category_id': cls.env.ref('odex30_account_avatax.DC010000').id,
        })
        cls.product_accounting = cls.env["product.product"].create({
            'name': "Accounting",
            'list_price': 30.00,
            'standard_price': 30.00,
            'supplier_taxes_id': None,
            'avatax_category_id': cls.env.ref('odex30_account_avatax.DC010000').id,
        })
        cls.product_expenses = cls.env["product.product"].create({
            'name': "Expenses",
            'list_price': 15.00,
            'standard_price': 15.00,
            'supplier_taxes_id': None,
            'avatax_category_id': cls.env.ref('odex30_account_avatax.DC010000').id,
        })
        cls.product_invoicing = cls.env["product.product"].create({
            'name': "Invoicing",
            'list_price': 15.00,
            'standard_price': 15.00,
            'supplier_taxes_id': None,
            'avatax_category_id': cls.env.ref('odex30_account_avatax.DC010000').id,
        })


        cls.example_tax = cls.env["account.tax"].create({
            'name': 'CA STATE 6%',
            'company_id': cls.env.user.company_id.id,
            'amount': 1,
            'amount_type': 'percent',
        })

        return res

    @classmethod
    def _create_invoice(cls, post=True, **kwargs):
        invoice = cls.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': cls.partner.id,
            'fiscal_position_id': cls.fp_avatax.id,
            'invoice_date': '2020-01-01',
            'invoice_line_ids': [
                (0, 0, {'product_id': cls.product.id, 'price_unit': 100}),
            ],
            **kwargs,
        })
        if post:
            invoice.action_post()
        return invoice

    @classmethod
    def _create_invoice_01_and_expected_response(cls):
        invoice = cls.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': cls.partner.id,
            'fiscal_position_id': cls.fp_avatax.id,
            'invoice_date': '2021-01-01',
            'invoice_line_ids': [
                (0, 0, {
                    'product_id': cls.product_user.id,
                    'tax_ids': None,
                    'price_unit': cls.product_user.list_price,
                }),
                (0, 0, {
                    'product_id': cls.product_user_discound.id,
                    'tax_ids': None,
                    'price_unit': cls.product_user_discound.list_price,
                }),
                (0, 0, {
                    'product_id': cls.product_accounting.id,
                    'tax_ids': None,
                    'price_unit': cls.product_accounting.list_price,
                }),
                (0, 0, {
                    'product_id': cls.product_expenses.id,
                    'tax_ids': None,
                    'price_unit': cls.product_expenses.list_price,
                }),
                (0, 0, {
                    'product_id': cls.product_invoicing.id,
                    'tax_ids': None,
                    'price_unit': cls.product_invoicing.list_price,
                }),
            ]
        })
        response = generate_response_invoice_1(invoice.invoice_line_ids)
        return invoice, response

    @classmethod
    def _create_invoice_02_and_expected_response(cls):
        invoice = cls.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': cls.partner.id,
            'fiscal_position_id': cls.fp_avatax.id,
            'invoice_line_ids': [
                (0, 0, {
                    'product_id': cls.product_user.id,
                    'tax_ids': None,
                    'price_unit': cls.product_user.list_price,
                    'discount': 1 / 7 * 100,
                }),
                (0, 0, {
                    'product_id': cls.product_accounting.id,
                    'tax_ids': None,
                    'price_unit': cls.product_accounting.list_price,
                }),
                (0, 0, {
                    'product_id': cls.product_expenses.id,
                    'tax_ids': None,
                    'price_unit': cls.product_expenses.list_price,
                }),
                (0, 0, {
                    'product_id': cls.product_invoicing.id,
                    'tax_ids': None,
                    'price_unit': cls.product_invoicing.list_price,
                }),
            ]
        })
        response = generate_response_invoice_2(invoice.invoice_line_ids)
        return invoice, response

    @classmethod
    def _create_invoice_03_and_expected_response(cls):
        invoice = cls.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': cls.partner.id,
            'fiscal_position_id': cls.fp_avatax.id,
            'invoice_line_ids': [
                Command.create({
                    'product_id': cls.product_accounting.id,
                    'tax_ids': None,
                    'price_unit': cls.product_accounting.list_price,
                }),
            ]
        })
        response = generate_response_invoice_3(invoice.invoice_line_ids)
        return invoice, response
