# -*- coding: utf-8 -*-

import markupsafe

from odoo import models, fields, api
from odoo.exceptions import UserError
from odoo.tools.misc import formatLang, format_date, get_lang
from odoo.tools.translate import _
from odoo.tools import html2plaintext, plaintext2html


class AccountFollowupReport(models.AbstractModel):
    _name = 'account.followup.report'
    _description = "Follow-up Report"

    ####################################################
    # REPORT COMPUTATION - TEMPLATE RENDERING
    ####################################################

    def get_followup_report_html(self, options):
        """
        Return the html of the followup report, based on the report options.
        """
        template = 'odex30_account_followup.template_followup_report'
        partner = self.env['res.partner'].browse(options['partner_id'])
        render_values = {
            'doc': partner,
            'lang': partner.lang or get_lang(self.env).code,
            'options': options,
            'context': self.env.context,
        }
        return self.env['ir.qweb']._render(template, render_values)

    def _get_followup_report_options(self, partner, options=None):
        """
        Compute the report options for a given partner.
        """
        options = options or {}
        options.update({
            'partner_id': partner.id,
            'followup_line': options.get('followup_line', partner.followup_line_id),
            'context': self.env.context,
        })
        return options

    def _get_followup_report_lines(self, options):
        """
        Compute and return the lines of the columns of the follow-ups report.
        """
        # Get date format for the lang
        partner = options.get('partner_id') and self.env['res.partner'].browse(options['partner_id']) or False
        if not partner:
            return []

        lang_code = partner.lang
        lines = []
        res = {}
        today = fields.Date.today()
        line_num = 0
        for l in partner.unreconciled_aml_ids.sorted().filtered(lambda aml: not aml.currency_id.is_zero(aml.amount_residual_currency)):
            if l.company_id in self.env.company.search([('id', 'child_of', self.env.context.get('allowed_company_ids', self.env.company.id))]):
                currency = l.currency_id or l.company_id.currency_id
                if currency not in res:
                    res[currency] = []
                res[currency].append(l)
        for currency, aml_recs in res.items():
            total = 0
            total_issued = 0
            for aml in aml_recs:
                amount = aml.amount_residual_currency if aml.currency_id else aml.amount_residual
                invoice_date = {
                    'name': format_date(self.env, aml.move_id.invoice_date or aml.date, lang_code=lang_code),
                    'class': 'date',
                    'style': 'white-space:nowrap;text-align:left;',
                    'template': 'odex30_account_followup.line_template',
                }
                date_due = format_date(self.env, aml.date_maturity, lang_code=lang_code)
                total += amount or 0
                is_overdue = today > aml.date_maturity if aml.date_maturity else False
                is_payment = aml.payment_id
                if is_overdue or is_payment:
                    total_issued += amount or 0
                date_due = {
                    'name': date_due, 'class': 'date',
                    'style': 'white-space:nowrap;text-align:left;',
                    'template': 'odex30_account_followup.line_template',
                }
                if is_overdue:
                    date_due['style'] += 'color: red;'
                if is_payment:
                    date_due = ''
                move_line_name = {
                    'name': self._followup_report_format_aml_name(aml.name, aml.move_id.ref),
                    'style': 'text-align:left; white-space:normal;',
                    'template': 'odex30_account_followup.line_template',
                }
                amount = {
                    'name': formatLang(self.env, amount, currency_obj=currency),
                    'style': 'text-align:right; white-space:normal;',
                    'template': 'odex30_account_followup.line_template',
                }
                line_num += 1
                invoice_origin = aml.move_id.invoice_origin or ''
                if len(invoice_origin) > 43:
                    invoice_origin = invoice_origin[:40] + '...'
                invoice_origin = {
                    'name': invoice_origin,
                    'style': 'text-align:center; white-space:normal;',
                    'template': 'account_followup.line_template',
                }
                columns = [
                    invoice_date,
                    date_due,
                    invoice_origin,
                    move_line_name,
                    amount,
                ]
                lines.append({
                    'id': aml.id,
                    'account_move': aml.move_id,
                    'name': aml.move_id.name,
                    'move_id': aml.move_id.id,
                    'type': is_payment and 'payment' or 'unreconciled_aml',
                    'unfoldable': False,
                    'columns': [isinstance(v, dict) and v or {'name': v, 'template': 'account_followup.line_template'} for v in columns],
                })
            total_due = formatLang(self.env, total, currency_obj=currency)
            line_num += 1

            cols = \
                [{
                    'name': v,
                    'template': 'account_followup.line_template',
                } for v in [''] * 3] + \
                [{
                    'name': v,
                    'style': 'text-align:right; white-space:normal; font-weight: bold;',
                    'template': 'account_followup.line_template',
                } for v in [total >= 0 and _('Total Due') or '', total_due]]

            lines.append({
                'id': line_num,
                'name': '',
                'class': 'total',
                'style': 'border-top-style: double',
                'unfoldable': False,
                'level': 3,
                'columns': cols,
            })
            if total_issued > 0:
                total_issued = formatLang(self.env, total_issued, currency_obj=currency)
                line_num += 1

                cols = \
                    [{
                        'name': v,
                        'template': 'account_followup.line_template',
                    } for v in [''] * 3] + \
                    [{
                        'name': v,
                        'style': 'text-align:right; white-space:normal; font-weight: bold;',
                        'template': 'account_followup.line_template',
                    } for v in [_('Total Overdue'), total_issued]]

                lines.append({
                    'id': line_num,
                    'name': '',
                    'class': 'total',
                    'unfoldable': False,
                    'level': 3,
                    'columns': cols,
                })
            # Add an empty line after the total to make a space between two currencies
            line_num += 1
            lines.append({
                'id': line_num,
                'name': '',
                'class': '',
                'style': 'border-bottom-style: none',
                'unfoldable': False,
                'level': 0,
                'columns': [{'template': 'odex30_account_followup.line_template'} for col in columns],
            })
        # Remove the last empty line
        if lines:
            lines.pop()

        # Catch negative numbers when present
        for line in lines:
            for col in line['columns']:
                if self.env.company.currency_id.compare_amounts(col.get('no_format', 0.0), 0.0) == -1:
                    col['class'] = 'number color-red'

        return lines

    @api.model
    def _followup_report_format_aml_name(self, line_name, move_ref, move_name=None):
        """ Format the display of an account.move.line record. As its very costly to fetch the account.move.line
        records, only line_name, move_ref, move_name are passed as parameters to deal with sql-queries more easily.

        :param line_name:   The name of the account.move.line record.
        :param move_ref:    The reference of the account.move record.
        :param move_name:   The name of the account.move record.
        :return:            The formatted name of the account.move.line record.
        """
        names = []
        if move_name is not None and move_name != '/':
            names.append(move_name)
        if move_ref and move_ref != '/':
            names.append(move_ref)
        if line_name and line_name != move_name and line_name != '/':
            names.append(line_name)
        name = '-'.join(names)
        return name

    ####################################################
    # DEFAULT BODY AND EMAIL SUBJECT
    ####################################################

    @api.model
    def _get_rendered_body(self, partner_id, template_src, default_body, **kwargs):
        """ Returns the body that can be rendered by the template_src, or if None, returns the default_body.
        kwargs can contain any keyword argument supported by the *_render_template* function
        """
        if template_src:
            return self.env['mail.composer.mixin'].sudo()._render_template(template_src, 'res.partner', [partner_id], **kwargs)[partner_id]

        return default_body

    @api.model
    def _get_sms_body(self, options):
        # Manual follow-up: return body from options
        if options.get('sms_body'):
            return options.get('sms_body')

        partner = self.env['res.partner'].browse(options.get('partner_id'))
        followup_line = options.get('followup_line', partner.followup_line_id)
        sms_template = options.get('sms_template') or followup_line.sms_template_id
        template_src = sms_template.with_context(lang=partner.lang or self.env.user.lang).body

        partner_followup_responsible_id = partner._get_followup_responsible()
        responsible_signature = html2plaintext(partner_followup_responsible_id.signature or partner_followup_responsible_id.name)
        self = self.with_context(lang=partner.lang or self.env.user.lang)
        default_body = _("Dear client, we kindly remind you that you still have unpaid invoices. Please check them and take appropriate action. %s", responsible_signature)

        return self._get_rendered_body(partner.id, template_src, default_body, options={'post_process': True})

    @api.model
    def _get_email_from(self, options):
        partner = self.env['res.partner'].browse(options.get('partner_id'))
        # For manual followups, get the email_from from the selected template in the send & print wizard.
        if options.get('email_from'):
            followup_email_from = options['email_from']
        # For automatic followups, get it from mail template on the followup line.
        else:
            followup_line = options.get('followup_line') or partner.followup_line_id
            mail_template = options.get('mail_template') or followup_line.mail_template_id
            followup_email_from = mail_template.email_from
        # The _render_template() function formats the email content. It handles cases where the email_from value
        # is a template that needs evaluation. For instance, "{{ object._get_followup_responsible().email_formatted }}".
        return self.env['mail.composer.mixin'].sudo()._render_template(followup_email_from, 'res.partner', [partner.id])[partner.id] or None

    @api.model
    def _get_email_reply_to(self, options):
        partner = self.env['res.partner'].browse(options.get('partner_id'))
        followup_line = options.get('followup_line', partner.followup_line_id)
        mail_template = options.get('mail_template', followup_line.mail_template_id)
        # if template has no reply-to set, fall back to default reply-to, otherwise
        # it will be set to False and behave unexpectedly
        if mail_template.reply_to:
            followup_reply_to = mail_template.reply_to
        else:
            followup_reply_to = self._notify_get_reply_to()[False]
        return followup_reply_to

    @api.model
    def _get_main_body(self, options):
        # Manual follow-up: return body from options
        if options.get('body'):
            return options.get('body')

        partner = self.env['res.partner'].browse(options.get('partner_id'))
        followup_line = options.get('followup_line', partner.followup_line_id)
        mail_template = options.get('mail_template', followup_line.mail_template_id)
        template_src = None
        if mail_template:
            template_src = mail_template.with_context(lang=partner.lang or self.env.user.lang).body_html

        partner_followup_responsible_id = partner._get_followup_responsible()
        responsible_signature = partner_followup_responsible_id.signature or partner_followup_responsible_id.name
        self = self.with_context(lang=partner.lang or self.env.user.lang)
        default_body = _("""Dear %s,


Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take appropriate measures in order to carry out this payment in the next 8 days.

Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to contact our accounting department.

Best Regards,

""", partner.name)

        default_body_html = plaintext2html(default_body) + responsible_signature  # responsible_signature is an html field
        return self._get_rendered_body(partner.id, template_src, default_body_html, engine='qweb', options={'post_process': True})

    @api.model
    def _get_email_subject(self, options):
        # Manual follow-up: return body from options
        if options.get('email_subject'):
            return options.get('email_subject')

        partner = self.env['res.partner'].browse(options.get('partner_id'))
        followup_line = options.get('followup_line', partner.followup_line_id)
        mail_template = options.get('mail_template', followup_line.mail_template_id)
        template_src = None
        if mail_template:
            template_src = mail_template.with_context(lang=partner.lang or self.env.user.lang).subject

        partner_name = partner.name
        company_name = self.env.company.name
        self = self.with_context(lang=partner.lang or self.env.user.lang)
        default_body = _("%(company)s Payment Reminder - %(partner)s", company=company_name, partner=partner_name)

        return self._get_rendered_body(partner.id, template_src, default_body, options={'post_process': True})

    @api.model
    def _get_email_recipients(self, options):
        if options.get('email_recipient_ids'):
            return options.get('email_recipient_ids')

        partner = self.env['res.partner'].browse(options.get('partner_id'))
        recipients = partner._get_all_followup_contacts() or partner
        followup_line = options.get('followup_line', recipients.followup_line_id)
        mail_template = options.get('mail_template', followup_line.mail_template_id)
        if mail_template:
            rendered_values = mail_template._generate_template_recipients(
                [partner.id],
                {'partner_to', 'email_cc', 'email_to'},
                True)[partner.id]
            recipients |= partner.browse(rendered_values.get('partner_ids'))
        return recipients

    ####################################################
    # REPORT DATA
    ####################################################

    def _get_followup_report_columns_name(self):
        """
        Return the name of the columns of the follow-ups report
        """
        return [
            {'name': _('Reference'), 'style': 'text-align:center; white-space:nowrap;'},
            {'name': _('Date'), 'class': 'date', 'style': 'text-align:center; white-space:nowrap;'},
            {'name': _('Due Date'), 'class': 'date', 'style': 'text-align:center; white-space:nowrap;'},
            {'name': _('Origin'), 'style': 'text-align:center; white-space:nowrap;'},
            {'name': _('Communication'), 'style': 'text-align:center; white-space:nowrap;'},
            {'name': _('Total Due'), 'class': 'number o_price_total', 'style': 'text-align:center; white-space:nowrap;'},
        ]

    ####################################################
    # EXPORT
    ####################################################

    @api.model
    def _send_sms(self, options):
        """
        Send by SMS the followup to the customer
        """
        partner = self.env['res.partner'].browse(options.get('partner_id'))
        followup_contacts = partner._get_all_followup_contacts() or partner
        sent_at_least_once = False
        for to_send_partner in followup_contacts:
            sms_number = to_send_partner.mobile or to_send_partner.phone
            if sms_number:
                sms_body = self.with_context(lang=partner.lang or self.env.user.lang)._get_sms_body(options)
                partner._message_sms(
                    body=sms_body,
                    partner_ids=partner.ids,
                    sms_pid_to_number={partner.id: sms_number},
                )
                sent_at_least_once = True
        if not sent_at_least_once:
            raise UserError(_("You are trying to send an SMS, but no follow-up contact has any mobile/phone number set for customer '%s'", partner.name))

    @api.model
    def _send_email(self, options):
        """
        Send by email the followup to the customer's followup contacts
        """
        partner = self.env['res.partner'].browse(options.get('partner_id'))
        followup_line = options.get('followup_line', partner.followup_line_id)
        sent_at_least_once = False
        for to_send_partner in self._get_email_recipients(options):
            email = to_send_partner.email
            if email and email.strip():
                self = self.with_context(lang=partner.lang or self.env.user.lang)
                body_html = self.with_context(mail=True).get_followup_report_html(options)

                # Should contain the followup report and invoice attachments if join_invoices is True.
                attachment_ids = options.get('attachment_ids')

                # If the follow-up was executed manually, the author_id will be set to the ID of the current logged-in user.
                # Otherwise, if the follow-up is automatic, the author_id will be the followup responsible or OdooBot.
                author_id = options.get('author_id', partner._get_followup_responsible().partner_id.id)

                partner.with_context(mail_post_autofollow=True, mail_notify_author=True, lang=partner.lang or self.env.user.lang).message_post(
                    partner_ids=[to_send_partner.id],
                    author_id=author_id,
                    email_from=self._get_email_from(options),
                    body=body_html,
                    subject=self._get_email_subject(options),
                    reply_to=self._get_email_reply_to(options),
                    model_description=_('payment reminder'),
                    email_layout_xmlid='mail.mail_notification_light',
                    attachment_ids=attachment_ids,
                    subtype_id=self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note'),
                )
                sent_at_least_once = True

                # add additional followers to the partner's chatter
                if followup_line and followup_line.additional_follower_ids:
                    partner.message_subscribe(followup_line.additional_follower_ids.partner_id.ids)
        if not sent_at_least_once:
            raise UserError(_("You are trying to send an Email, but no follow-up contact has any email address set for customer '%s'", partner.name))

    @api.model
    def _print_followup_letter(self, partner, options=None):
        """Generate the followup letter for the given partner.
        The letter is saved as ir.attachment and linked in the chatter.

        Returns a client action downloading this letter and closing the wizard.
        """
        attachment = partner._get_followup_report_attachment(options)
        partner.message_post(body=_('Follow-up letter generated'), attachment_ids=[attachment.id])
        return {
            'type': 'ir.actions.client',
            'tag': 'close_followup_wizard',
            'params': {
                'url': '/web/content/%s?download=1' % attachment.id,
            }
        }
