"""
Fusion Accounting - Payment QR Codes

Extends ``account.move`` to generate **EPC QR codes** (European Payments
Council Quick Response Code) on invoices, allowing customers to scan
and pay directly from their banking app.

The EPC QR format is defined by the
`European Payments Council — Guidelines for QR Code
<https://www.europeanpaymentscouncil.eu/document-library/guidance-documents/quick-response-code-guidelines-enable-data-capture-initiation>`_.

Dependencies
------------
Requires the ``qrcode`` Python library (declared in the module manifest
under ``external_dependencies``).
"""

import base64
import io
import logging
import re

from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError

_log = logging.getLogger(__name__)

try:
    import qrcode
    from qrcode.constants import ERROR_CORRECT_M
except ImportError:
    qrcode = None
    _log.warning(
        "The 'qrcode' Python library is not installed. "
        "EPC QR code generation will be unavailable."
    )

# IBAN validation (basic structural check)
_IBAN_RE = re.compile(r'^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$')


class FusionPaymentQR(models.Model):
    """Adds EPC QR code generation to customer invoices.

    The EPC QR standard encodes payment instructions into a QR code
    that European banking apps can read to pre-fill a SEPA Credit
    Transfer.

    EPC QR Data Format
    ------------------
    The payload is a UTF-8 string with fields separated by newlines::

        BCD                         # Service Tag (fixed)
        002                         # Version
        1                           # Character Set (1 = UTF-8)
        SCT                         # Identification (SEPA Credit Transfer)
        <BIC>                       # BIC of the beneficiary bank
        <Name>                      # Beneficiary name (max 70 chars)
        <IBAN>                      # Beneficiary IBAN
        EUR<Amount>                 # Amount with currency prefix
        <Purpose>                   # Purpose code (optional, max 4 chars)
        <Reference>                 # Remittance reference (max 35 chars)
        <Unstructured Reference>    # Unstructured remittance info (max 140)
        <Information>               # Beneficiary to originator info (optional)
    """

    _inherit = 'account.move'

    # ------------------------------------------------------------------
    # Fields
    # ------------------------------------------------------------------

    fusion_qr_code_image = fields.Binary(
        string='Payment QR Code',
        compute='_compute_fusion_qr_code',
        help="EPC QR code for this invoice.  Customers can scan this "
             "with their banking app to initiate payment.",
    )
    fusion_qr_code_available = fields.Boolean(
        string='QR Code Available',
        compute='_compute_fusion_qr_code',
        help="Indicates whether a QR code can be generated for this "
             "invoice (depends on IBAN / BIC configuration).",
    )

    # ------------------------------------------------------------------
    # Computed fields
    # ------------------------------------------------------------------

    @api.depends(
        'state', 'move_type', 'amount_residual',
        'company_id', 'partner_id', 'currency_id',
    )
    def _compute_fusion_qr_code(self):
        """Compute the QR code image for eligible invoices."""
        for move in self:
            if (
                move.move_type in ('out_invoice', 'out_refund')
                and move.state == 'posted'
                and move.amount_residual > 0
                and qrcode is not None
            ):
                try:
                    qr_bytes = move.generate_epc_qr()
                    move.fusion_qr_code_image = base64.b64encode(qr_bytes)
                    move.fusion_qr_code_available = True
                except (UserError, ValidationError):
                    move.fusion_qr_code_image = False
                    move.fusion_qr_code_available = False
            else:
                move.fusion_qr_code_image = False
                move.fusion_qr_code_available = False

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def generate_epc_qr(self):
        """Generate an EPC QR code image for this invoice.

        The QR code encodes payment instructions following the
        European Payments Council standard so that banking apps
        can pre-fill a SEPA Credit Transfer form.

        :return: PNG image bytes of the QR code.
        :rtype: bytes
        :raises UserError: if the ``qrcode`` library is not installed
            or required bank details are missing.
        """
        self.ensure_one()

        if qrcode is None:
            raise UserError(_(
                "The 'qrcode' Python library is required to generate "
                "payment QR codes.  Install it with: pip install qrcode[pil]"
            ))

        # Gather bank details
        company = self.company_id
        company_bank = company.partner_id.bank_ids[:1]
        if not company_bank:
            raise UserError(_(
                "No bank account is configured for company '%(company)s'. "
                "Please add a bank account with an IBAN in the company "
                "settings.",
                company=company.name,
            ))

        iban = (company_bank.acc_number or '').upper().replace(' ', '')
        if not _IBAN_RE.match(iban):
            raise UserError(_(
                "The company bank account '%(acc)s' does not appear to "
                "be a valid IBAN.",
                acc=company_bank.acc_number,
            ))

        bic = (company_bank.bank_bic or '').upper().replace(' ', '')

        # Amount and currency
        if self.currency_id.name != 'EUR':
            raise UserError(_(
                "EPC QR codes are only supported for invoices in EUR. "
                "This invoice uses %(currency)s.",
                currency=self.currency_id.name,
            ))

        amount = self.amount_residual
        if amount <= 0 or amount > 999999999.99:
            raise UserError(_(
                "Amount must be between 0.01 and 999,999,999.99 for "
                "EPC QR codes."
            ))

        # Build EPC QR payload
        beneficiary_name = (company.name or '')[:70]
        reference = (self.payment_reference or self.name or '')[:35]
        unstructured = ''
        if not reference:
            unstructured = (
                f'Invoice {self.name}' if self.name else ''
            )[:140]

        # EPC QR payload lines (Version 002)
        lines = [
            'BCD',                                  # Service Tag
            '002',                                  # Version
            '1',                                    # Character set (UTF-8)
            'SCT',                                  # Identification code
            bic,                                    # BIC (may be empty)
            beneficiary_name,                       # Beneficiary name
            iban,                                   # Beneficiary IBAN
            f'EUR{amount:.2f}',                     # Amount
            '',                                     # Purpose (optional)
            reference if reference else '',         # Structured reference
            unstructured if not reference else '',  # Unstructured ref
            '',                                     # Beneficiary info (optional)
        ]
        payload = '\n'.join(lines)

        # Validate payload size (EPC QR max 331 bytes)
        if len(payload.encode('utf-8')) > 331:
            raise UserError(_(
                "The QR code payload exceeds the EPC maximum of 331 "
                "bytes.  Please shorten the payment reference."
            ))

        # Generate QR code image
        qr = qrcode.QRCode(
            version=None,  # auto-size
            error_correction=ERROR_CORRECT_M,
            box_size=10,
            border=4,
        )
        qr.add_data(payload)
        qr.make(fit=True)
        img = qr.make_image(fill_color='black', back_color='white')

        # Export to PNG bytes
        buffer = io.BytesIO()
        img.save(buffer, format='PNG')
        return buffer.getvalue()
