# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools.translate import LazyTranslate

from odoo.addons.payment import utils as payment_utils

_lt = LazyTranslate(__name__, default_lang='en_US')

REPORT_REASONS_MAPPING = {
    'pricelist_not_allowed': _lt("pricelist not allowed"),
}


class PaymentProvider(models.Model):
    _inherit = 'payment.provider'

    journal_id = fields.Many2one(
        string="Payment Journal",
        help="The journal in which the successful transactions are posted.",
        comodel_name='account.journal',
        compute='_compute_journal_id',
        inverse='_inverse_journal_id',
        check_company=True,
        domain='[("type", "=", "bank")]',
        copy=False,
    )
    available_pricelist_ids = fields.Many2many(
        string="Pricelists",
        help="Only allow this payment provider when the partner's pricelist matches one of these.",
        comodel_name='product.pricelist',
    )

    #=== COMPUTE METHODS ===#

    def _ensure_payment_method_line(self, allow_create=True):
        self.ensure_one()
        if not self.id:
            return

        default_payment_method = self._get_provider_payment_method(self._get_code())
        if not default_payment_method:
            return

        pay_method_line = self.env['account.payment.method.line'].search([
            ('payment_provider_id', '=', self.id),
            ('journal_id', '!=', False),
        ], limit=1)

        if not self.journal_id:
            if pay_method_line:
                pay_method_line.unlink()
                return

        if not pay_method_line:
            pay_method_line = self.env['account.payment.method.line'].search(
                [
                    *self.env['account.payment.method.line']._check_company_domain(self.company_id),
                    ('code', '=', self._get_code()),
                    ('payment_provider_id', '=', False),
                    ('journal_id', '!=', False),
                ],
                limit=1,
            )
        if pay_method_line:
            pay_method_line.payment_provider_id = self
            pay_method_line.journal_id = self.journal_id
            pay_method_line.name = self.name
        elif allow_create:
            create_values = {
                'name': self.name,
                'payment_method_id': default_payment_method.id,
                'journal_id': self.journal_id.id,
                'payment_provider_id': self.id,
                'payment_account_id': self._get_payment_method_outstanding_account_id(default_payment_method)
            }
            pay_method_line_same_code = self.env['account.payment.method.line'].search(
                [
                    *self.env['account.payment.method.line']._check_company_domain(self.company_id),
                    ('code', '=', self._get_code()),
                ],
                limit=1,
            )
            if pay_method_line_same_code:
                create_values['payment_account_id'] = pay_method_line_same_code.payment_account_id.id
            if self._get_code() == 'sepa_direct_debit':
                create_values['name'] = "Online SEPA"
            self.env['account.payment.method.line'].create(create_values)

    def _get_payment_method_outstanding_account_id(self, payment_method_id):
        if self.code == 'custom':
            return False
        account_ref = 'account_journal_payment_debit_account_id' if payment_method_id.payment_type == 'inbound' else 'account_journal_payment_credit_account_id'
        chart_template = self.with_context(allowed_company_ids=self.company_id.root_id.ids).env['account.chart.template']
        outstanding_account_id = (
            chart_template.ref(account_ref, raise_if_not_found=False)
            or self.company_id.transfer_account_id
        ).id
        return outstanding_account_id

    @api.depends('code', 'state', 'company_id')
    def _compute_journal_id(self):
        for provider in self:
            pay_method_line = self.env['account.payment.method.line'].search([
                ('payment_provider_id', '=', provider._origin.id),
                ('journal_id', '!=', False),
            ], limit=1)

            if pay_method_line:
                provider.journal_id = pay_method_line.journal_id
            elif provider.state in ('enabled', 'test'):
                provider.journal_id = self.env['account.journal'].search(
                    [
                        ('company_id', '=', provider.company_id.id),
                        ('type', '=', 'bank'),
                    ],
                    limit=1,
                )
                if provider.id:
                    provider._ensure_payment_method_line()

    def _inverse_journal_id(self):
        for provider in self:
            provider._ensure_payment_method_line()

    @api.model
    def _get_provider_payment_method(self, code):
        return self.env['account.payment.method'].search([('code', '=', code)], limit=1)

    # === BUSINESS METHODS === #

    @api.model
    def _get_compatible_providers(self, company_id, partner_id, *args, report=None, **kwargs):
        """Override of `payment` to exclude providers with incompatible pricelists.

        :param int company_id: The company to which providers must belong, as a `res.company` id.
        :param int partner_id: The partner making the payment, as a `res.partner` id.
        :param dict report: The availability report.
        :return: The compatible providers
        :rtype: payment.provider
        """
        compatible_providers = super()._get_compatible_providers(
            company_id, partner_id, *args, report=report, **kwargs
        )
        if pricelist := self.env['res.partner'].browse(partner_id).property_product_pricelist:
            unfiltered_providers = compatible_providers
            compatible_providers = compatible_providers.filtered(
                lambda p: not p.available_pricelist_ids or pricelist in p.available_pricelist_ids
            )
            payment_utils.add_to_report(
                report,
                unfiltered_providers - compatible_providers,
                available=False,
                reason=REPORT_REASONS_MAPPING['pricelist_not_allowed'],
            )
        return compatible_providers

    #=== BUSINESS METHODS ===#

    @api.model
    def _setup_provider(self, code, **kwargs):
        """ Override of `payment` to create the payment method of the provider. """
        super()._setup_provider(code, **kwargs)
        self._setup_payment_method(code)

    @api.model
    def _setup_payment_method(self, code):
        if code not in ('none', 'custom') and not self._get_provider_payment_method(code):
            providers_description = dict(self._fields['code']._description_selection(self.env))
            self.env['account.payment.method'].sudo().create({
                'name': providers_description[code],
                'code': code,
                'payment_type': 'inbound',
            })

    def _check_existing_payment(self, payment_method):
        existing_payment_count = self.env['account.payment'].search_count([('payment_method_id', '=', payment_method.id)], limit=1)
        return bool(existing_payment_count)

    @api.model
    def _remove_provider(self, code, **kwargs):
        """ Override of `payment` to delete the payment method of the provider. """
        payment_method = self._get_provider_payment_method(code)
        # If the payment method is used by any payments, we block the uninstallation of the module.
        if self._check_existing_payment(payment_method):
            raise UserError(_("You cannot uninstall this module as payments using this payment method already exist."))
        super()._remove_provider(code, **kwargs)
        payment_method.unlink()
