# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import math
from datetime import timedelta

from odoo import fields, models


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

    def _invoice_paid_hook(self):
        """Override: update membership_lines dates upon invoice payment.

        RF-01: Use the payment date instead of the invoice date for date_from.
        RF-02: Renewals: if the member has an active membership, the new one
               starts the day after the current one expires, avoiding overlap.
        RF-03: For variable products, recalculate date_to from the new date_from.
        """
        res = super()._invoice_paid_hook()
        for invoice in self.filtered(lambda m: m.move_type == "out_invoice"):
            membership_lines = self.env["membership.membership_line"].search(
                [
                    ("account_invoice_line", "in", invoice.invoice_line_ids.ids),
                    ("date_cancel", "=", False),
                ]
            )
            if not membership_lines:
                continue
            payment_date = self._get_membership_payment_date(invoice)
            for mline in membership_lines:
                partner = mline.partner
                product = mline.membership_id
                quantity = (
                    mline.account_invoice_line.quantity
                    if mline.account_invoice_line
                    else 1.0
                )
                date_from, date_to = self._compute_membership_dates(
                    partner,
                    product,
                    payment_date,
                    quantity,
                    exclude_line_ids=membership_lines.ids,
                )
                mline.write(
                    {
                        "date_from": date_from,
                        "date_to": date_to,
                    }
                )
        return res

    def _get_membership_payment_date(self, invoice):
        """Get the date of the most recent reconciled payment.

        If there are no payments (e.g. zero-amount invoice), fallback to today.
        """
        self.ensure_one()
        payments = invoice.reconciled_payment_ids
        if payments:
            return max(payments.mapped("date"))
        return fields.Date.today()

    def _compute_membership_dates(
        self, partner, product, payment_date, quantity=1.0,
        exclude_line_ids=None,
    ):
        """Calculate date_from and date_to for a membership line.

        :param partner: res.partner (member)
        :param product: product.product (membership product)
        :param payment_date: effective payment date
        :param quantity: product quantity on the invoice line
        :param exclude_line_ids: membership line IDs to exclude from the
            active membership search (to avoid matching the lines we are
            currently updating)
        :return: tuple (date_from, date_to)
        """
        domain = [
            ("partner", "=", partner.id),
            ("date_cancel", "=", False),
            ("state", "in", ("paid", "invoiced")),
            ("date_to", ">=", fields.Date.today()),
        ]
        if exclude_line_ids:
            domain.append(("id", "not in", exclude_line_ids))
        # RF-02: Look for an active membership (not cancelled, not expired)
        active_membership = self.env["membership.membership_line"].search(
            domain,
            limit=1,
            order="date_to desc",
        )

        if active_membership:
            # RF-02: Renewal — start the day after the current one expires
            date_from = active_membership.date_to + timedelta(days=1)
        else:
            # RF-01: New membership — use the payment date
            date_from = payment_date

        # RF-03: Calculate date_to according to membership type
        if product.membership_type == "variable":
            qty = int(math.ceil(quantity))
            next_date = product._get_next_date(date_from, qty=qty)
            date_to = next_date and (next_date - timedelta(days=1)) or False
        else:
            date_to = product.membership_date_to

        # Safety: respect the product's lower bound
        if product.membership_date_from and date_from < product.membership_date_from:
            date_from = product.membership_date_from

        # Safety: ensure date_from does not exceed date_to
        if date_to and date_from > date_to:
            if active_membership:
                date_from = date_to
            else:
                date_from = min(payment_date, date_to)

        return date_from, date_to
