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

from odoo import api, fields, models
from odoo.tools.float_utils import float_compare
from odoo.tools.sql import column_exists, create_column

# Available values for the release_to_pay field.
_release_to_pay_status_list = [('yes', 'Yes'), ('no', 'No'), ('exception', 'Exception')]

class AccountMove(models.Model):
    _inherit = 'account.move'

    def _auto_init(self):
        if not column_exists(self.env.cr, "account_move", "release_to_pay"):
            # Create column manually to set default value to 'exception' on postgres level.
            # This way we avoid heavy computation on module installation.
            self.env.cr.execute("ALTER TABLE account_move ADD COLUMN release_to_pay VARCHAR DEFAULT 'exception'")

        return super()._auto_init()

    release_to_pay = fields.Selection(
        _release_to_pay_status_list,
        compute='_compute_release_to_pay',
        copy=False,
        store=True,
        help="This field can take the following values :\n"
             "  * Yes: you should pay the bill, you have received the products\n"
             "  * No, you should not pay the bill, you have not received the products\n"
             "  * Exception, there is a difference between received and billed quantities\n"
             "This status is defined automatically, but you can force it by ticking the 'Force Status' checkbox.")
    release_to_pay_manual = fields.Selection(
        _release_to_pay_status_list,
        string='Should Be Paid',
        compute='_compute_release_to_pay_manual', store='True', readonly=False,
        help="  * Yes: you should pay the bill, you have received the products\n"
             "  * No, you should not pay the bill, you have not received the products\n"
             "  * Exception, there is a difference between received and billed quantities\n"
             "This status is defined automatically, but you can force it by ticking the 'Force Status' checkbox.")
    force_release_to_pay = fields.Boolean(
        string="Force Status",
        help="Indicates whether the 'Should Be Paid' status is defined automatically or manually.")

    @api.depends('invoice_line_ids.can_be_paid', 'force_release_to_pay', 'payment_state')
    def _compute_release_to_pay(self):
        records = self
        if self.env.context.get('module') == 'odex30_account_3way_match':
            # on module installation we set 'no' for all paid bills and other non relevant records at once
            records = records.filtered(lambda r: r.payment_state != 'paid' and r.move_type in ('in_invoice', 'in_refund'))
            (self - records).release_to_pay = 'no'
        for invoice in records:
            if invoice.payment_state == 'paid' or not invoice.is_invoice(include_receipts=True):
                # no need to pay, if it's already paid
                invoice.release_to_pay = 'no'
            elif invoice.force_release_to_pay:
                #we must use the manual value contained in release_to_pay_manual
                invoice.release_to_pay = invoice.release_to_pay_manual
            else:
                #otherwise we must compute the field
                result = None
                for invoice_line in invoice.invoice_line_ids.filtered(lambda l: l.display_type not in ('line_section', 'line_note')):
                    line_status = invoice_line.can_be_paid
                    if line_status == 'exception':
                        #If one line is in exception, the entire bill is
                        result = 'exception'
                        break
                    elif not result:
                        result = line_status
                    elif line_status != result:
                        result = 'exception'
                        break
                    #The last two elif conditions model the fact that a
                    #bill will be in exception if its lines have different status.
                    #Otherwise, its status will be the one all its lines share.

                #'result' can be None if the bill was entirely empty.
                invoice.release_to_pay = result or 'no'

    @api.depends('release_to_pay', 'force_release_to_pay')
    def _compute_release_to_pay_manual(self):
        for invoice in self:
            if not (invoice.payment_state == 'paid' or not invoice.is_invoice(include_receipts=True) or invoice.force_release_to_pay):
                invoice.release_to_pay_manual = invoice.release_to_pay

    @api.onchange('release_to_pay_manual')
    def _onchange_release_to_pay_manual(self):
        if self.release_to_pay and self.release_to_pay_manual != self.release_to_pay:
            self.force_release_to_pay = True


class AccountMoveLine(models.Model):
    _inherit = 'account.move.line'

    def _auto_init(self):
        if not column_exists(self.env.cr, "account_move_line", "can_be_paid"):
            # Create column manually to set default value to 'exception' on postgres level.
            # This way we avoid heavy computation on module installation.
            self.env.cr.execute("ALTER TABLE account_move_line ADD COLUMN can_be_paid VARCHAR DEFAULT 'exception'")

        return super()._auto_init()


    @api.depends('purchase_line_id.qty_received', 'purchase_line_id.qty_invoiced', 'purchase_line_id.product_qty', 'price_unit')
    def _can_be_paid(self):
        """ Computes the 'release to pay' status of an invoice line, depending on
        the invoicing policy of the product linked to it, by calling the dedicated
        subfunctions. This function also ensures the line is linked to a purchase
        order (otherwise, can_be_paid will be set as 'exception'), and the price
        between this order and the invoice did not change (otherwise, again,
        the line is put in exception).
        """
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        for invoice_line in self:
            po_line = invoice_line.purchase_line_id
            if po_line:
                invoiced_qty = po_line.qty_invoiced
                received_qty = po_line.qty_received
                ordered_qty = po_line.product_qty

                # A price difference between the original order and the invoice results in an exception
                invoice_currency = invoice_line.currency_id
                order_currency = po_line.currency_id
                invoice_converted_price = invoice_currency._convert(
                    invoice_line.price_unit, order_currency, invoice_line.company_id, fields.Date.today())
                if order_currency.compare_amounts(po_line.price_unit, invoice_converted_price) != 0:
                    invoice_line.can_be_paid = 'exception'
                    continue

                if po_line.product_id.purchase_method == 'purchase': # 'on ordered quantities'
                    invoice_line._can_be_paid_ordered_qty(invoiced_qty, received_qty, ordered_qty, precision)
                else: # 'on received quantities'
                    invoice_line._can_be_paid_received_qty(invoiced_qty, received_qty, ordered_qty, precision)

            else: # Serves as default if the line is not linked to any Purchase.
                invoice_line.can_be_paid = 'exception'

    def _can_be_paid_ordered_qty(self, invoiced_qty, received_qty, ordered_qty, precision):
        """
        Gives the release_to_pay status of an invoice line for 'on ordered
        quantity' billing policy, if this line's invoice is related to a purchase order.

        This function sets can_be_paid field to one of the following:
        'yes': the content of the line has been ordered and can be invoiced
        'no' : the content of the line hasn't been ordered at all, and cannot be invoiced
        'exception' : only part of the invoice has been ordered
        """
        if float_compare(invoiced_qty - self.quantity, ordered_qty, precision_digits=precision) >= 0:
            self.can_be_paid = 'no'
        elif float_compare(invoiced_qty, ordered_qty, precision_digits=precision) <= 0:
            self.can_be_paid = 'yes'
        else:
            self.can_be_paid = 'exception'

    def _can_be_paid_received_qty(self, invoiced_qty, received_qty, ordered_qty, precision):
        """
        Gives the release_to_pay status of an invoice line for 'on received
        quantity' billing policy, if this line's invoice is related to a purchase order.

        This function sets can_be_paid field to one of the following:
        'yes': the content of the line has been received and can be invoiced
        'no' : the content of the line hasn't been received at all, and cannot be invoiced
        'exception' : ordered and received quantities differ
        """
        if float_compare(invoiced_qty, received_qty, precision_digits=precision) <= 0:
            self.can_be_paid = 'yes'
        elif received_qty == 0 and float_compare(invoiced_qty, ordered_qty, precision_digits=precision) <= 0: # "and" part to ensure a too high billed quantity results in an exception:
            self.can_be_paid = 'no'
        else:
            self.can_be_paid = 'exception'

    can_be_paid = fields.Selection(
        _release_to_pay_status_list,
        compute='_can_be_paid',
        copy=False,
        store=True,
        string='Release to Pay')
