"""
Fusion Accounting - Batch Payment Processing

Provides the ``fusion.batch.payment`` model which allows grouping
multiple vendor or customer payments into a single batch for
streamlined bank submission and reconciliation.
"""

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


class FusionBatchPayment(models.Model):
    """Groups individual payments into batches for bulk processing.

    A batch payment collects payments that share the same journal and
    payment method so they can be sent to the bank as a single file
    or printed on a single check run.
    """

    _name = 'fusion.batch.payment'
    _description = 'Batch Payment'
    _order = 'date desc, id desc'
    _inherit = ['mail.thread', 'mail.activity.mixin']

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

    name = fields.Char(
        string='Reference',
        required=True,
        copy=False,
        readonly=True,
        default='/',
        tracking=True,
        help="Unique reference for this batch payment.",
    )
    journal_id = fields.Many2one(
        comodel_name='account.journal',
        string='Bank Journal',
        required=True,
        domain="[('type', '=', 'bank')]",
        tracking=True,
        help="The bank journal used for all payments in this batch.",
    )
    payment_method_id = fields.Many2one(
        comodel_name='account.payment.method',
        string='Payment Method',
        required=True,
        tracking=True,
        help="Payment method shared by every payment in the batch.",
    )
    payment_ids = fields.Many2many(
        comodel_name='account.payment',
        relation='fusion_batch_payment_rel',
        column1='batch_id',
        column2='payment_id',
        string='Payments',
        copy=False,
        help="Individual payments included in this batch.",
    )
    state = fields.Selection(
        selection=[
            ('draft', 'Draft'),
            ('sent', 'Sent'),
            ('reconciled', 'Reconciled'),
        ],
        string='Status',
        default='draft',
        required=True,
        readonly=True,
        copy=False,
        tracking=True,
        help="Draft: batch is being assembled.\n"
             "Sent: batch has been transmitted to the bank.\n"
             "Reconciled: all payments in the batch are reconciled.",
    )
    date = fields.Date(
        string='Date',
        required=True,
        default=fields.Date.context_today,
        tracking=True,
        help="Effective date of the batch payment.",
    )
    amount_total = fields.Monetary(
        string='Total Amount',
        compute='_compute_amount_total',
        store=True,
        currency_field='currency_id',
        help="Sum of all payment amounts in this batch.",
    )
    currency_id = fields.Many2one(
        comodel_name='res.currency',
        string='Currency',
        related='journal_id.currency_id',
        readonly=True,
        store=True,
        help="Currency of the bank journal.",
    )
    company_id = fields.Many2one(
        comodel_name='res.company',
        string='Company',
        related='journal_id.company_id',
        store=True,
        readonly=True,
    )
    payment_count = fields.Integer(
        string='Payment Count',
        compute='_compute_amount_total',
        store=True,
    )

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

    @api.depends('payment_ids', 'payment_ids.amount')
    def _compute_amount_total(self):
        """Compute the total batch amount and payment count."""
        for batch in self:
            batch.amount_total = sum(batch.payment_ids.mapped('amount'))
            batch.payment_count = len(batch.payment_ids)

    # ------------------------------------------------------------------
    # Constraints
    # ------------------------------------------------------------------

    @api.constrains('payment_ids')
    def _check_payments_journal(self):
        """Ensure every payment belongs to the same journal and uses the
        same payment method as the batch."""
        for batch in self:
            for payment in batch.payment_ids:
                if payment.journal_id != batch.journal_id:
                    raise ValidationError(_(
                        "Payment '%(payment)s' uses journal '%(pj)s' but "
                        "the batch requires journal '%(bj)s'.",
                        payment=payment.display_name,
                        pj=payment.journal_id.display_name,
                        bj=batch.journal_id.display_name,
                    ))
                if payment.payment_method_id != batch.payment_method_id:
                    raise ValidationError(_(
                        "Payment '%(payment)s' uses payment method '%(pm)s' "
                        "which differs from the batch method '%(bm)s'.",
                        payment=payment.display_name,
                        pm=payment.payment_method_id.display_name,
                        bm=batch.payment_method_id.display_name,
                    ))

    # ------------------------------------------------------------------
    # CRUD overrides
    # ------------------------------------------------------------------

    @api.model_create_multi
    def create(self, vals_list):
        """Assign a sequence number when creating a new batch."""
        for vals in vals_list:
            if vals.get('name', '/') == '/':
                vals['name'] = self.env['ir.sequence'].next_by_code(
                    'fusion.batch.payment'
                ) or _('New')
        return super().create(vals_list)

    # ------------------------------------------------------------------
    # Actions / Business Logic
    # ------------------------------------------------------------------

    def validate_batch(self):
        """Validate the batch and mark it as *Sent*.

        All payments in the batch must be in the *posted* state before
        the batch can be validated.

        :raises UserError: if the batch contains no payments or if any
            payment is not posted.
        """
        self.ensure_one()
        if self.state != 'draft':
            raise UserError(_("Only draft batches can be validated."))
        if not self.payment_ids:
            raise UserError(_(
                "Cannot validate an empty batch. Please add payments first."
            ))
        non_posted = self.payment_ids.filtered(lambda p: p.state != 'posted')
        if non_posted:
            raise UserError(_(
                "The following payments are not posted and must be confirmed "
                "before the batch can be validated:\n%(payments)s",
                payments=', '.join(non_posted.mapped('name')),
            ))
        self.write({'state': 'sent'})

    def action_draft(self):
        """Reset a sent batch back to draft state."""
        self.ensure_one()
        if self.state != 'sent':
            raise UserError(_("Only sent batches can be reset to draft."))
        self.write({'state': 'draft'})

    def action_reconcile(self):
        """Mark the batch as reconciled once bank confirms all payments."""
        self.ensure_one()
        if self.state != 'sent':
            raise UserError(_(
                "Only sent batches can be marked as reconciled."
            ))
        self.write({'state': 'reconciled'})

    def print_batch(self):
        """Generate a printable report for this batch payment.

        :return: Action dictionary triggering the report download.
        :rtype: dict
        """
        self.ensure_one()
        return self.env.ref(
            'fusion_accounting.action_report_batch_payment'
        ).report_action(self)

    @api.model
    def create_batch_from_payments(self, payment_ids):
        """Create a new batch payment from an existing set of payments.

        All supplied payments must share the same journal and payment
        method.

        :param payment_ids: recordset or list of ``account.payment`` ids
        :return: newly created ``fusion.batch.payment`` record
        :raises UserError: when payments do not share journal / method
        """
        if isinstance(payment_ids, (list, tuple)):
            payments = self.env['account.payment'].browse(payment_ids)
        else:
            payments = payment_ids

        if not payments:
            raise UserError(_("No payments were provided."))

        journals = payments.mapped('journal_id')
        methods = payments.mapped('payment_method_id')
        if len(journals) > 1:
            raise UserError(_(
                "All payments must belong to the same bank journal to "
                "be batched together."
            ))
        if len(methods) > 1:
            raise UserError(_(
                "All payments must use the same payment method."
            ))

        return self.create({
            'journal_id': journals.id,
            'payment_method_id': methods.id,
            'payment_ids': [(6, 0, payments.ids)],
        })
