# Copyright 2026 - Today Kidekoop
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

from odoo import fields  # type: ignore
from odoo import models  # type: ignore
from odoo.tools import float_round  # type: ignore

_logger = logging.getLogger(__name__)


class ProductPricelistItem(models.Model):
    _inherit = "product.pricelist.item"

    use_total_margin = fields.Boolean(
        string="Total Margin Mode",
        default=False,
        help="If checked, margins will be accumulated additively across the pricelist "
        "chain instead of being compounded.\n\n"
        "Example: Base price 100€, Margin1 -5%, Margin2 25%\n"
        "- Compound (default): 100 * 0.95 * 1.25 = 118.75€\n"
        "- Total (this option): 100 * (1 + 0.20) = 120€",
    )

    margin_type = fields.Selection(
        selection=[
            ("markup", "Markup (on cost)"),
            ("margin", "Commercial Margin (on PVP)"),
        ],
        string="Calculation Method",
        default="markup",
        help="Type of margin calculation:\n"
        "- Markup: PVP = Cost × (1 + markup%). Margin is calculated on cost.\n"
        "  Example: Cost 100€, Markup 25% → PVP = 125€\n\n"
        "- Commercial Margin: PVP = Cost / (1 - margin%). Margin is calculated on PVP.\n"
        "  Example: Cost 100€, Margin 20% → PVP = 125€ (margin = 25€/125€ = 20%)",
    )

    def _get_base_price_and_margins(self, product, quantity, uom, date, currency):
        """
        Traverse the pricelist chain to get the original base price and collect
        all margins in the chain.

        Returns:
            tuple: (base_price: float, margins: list of floats)
        """
        margins = []
        current_item = self
        visited_items = set()

        _logger.info(
            "[TOTAL MARGIN] Starting chain traversal for product %s [%s]",
            product.default_code or product.name,
            product.id,
        )

        # Traverse the chain backwards to collect all margins
        while current_item:
            # Prevent infinite loops
            if current_item.id in visited_items:
                _logger.warning(
                    "[TOTAL MARGIN] Circular reference detected in pricelist chain at item %s",
                    current_item.id,
                )
                break
            visited_items.add(current_item.id)

            # Collect this item's margin
            if current_item.compute_price == "formula":
                if current_item.base == "standard_price":
                    # For standard_price, use price_markup (inverted)
                    margin = current_item.price_markup or 0.0
                else:
                    # For other bases, use price_discount (negative = markup)
                    margin = -(current_item.price_discount or 0.0)

                margins.append(margin)
                _logger.info(
                    "[TOTAL MARGIN] Item %s: base=%s, margin=%.2f%%",
                    current_item.id,
                    current_item.base,
                    margin,
                )

            # Move to next item in chain
            if current_item.base == "pricelist" and current_item.base_pricelist_id:
                # Get the applicable rule from the base pricelist
                base_pricelist = current_item.base_pricelist_id
                rules = base_pricelist._get_applicable_rules(
                    products=product,
                    date=date,
                    quantity=quantity,
                    uom=uom,
                )
                if rules:
                    # Get the first (highest priority) rule
                    current_item = rules[0]
                else:
                    _logger.warning(
                        "[TOTAL MARGIN] No applicable rules found in base pricelist %s",
                        base_pricelist.id,
                    )
                    current_item = None
            else:
                # We've reached the base of the chain
                break

        # Now get the original base price (without any margins)
        if current_item:
            # Use the last item's base to compute the original price
            base_price = self._compute_original_base_price(
                current_item, product, quantity, uom, date, currency
            )
            _logger.info(
                "[TOTAL MARGIN] Original base price: %.2f (from item %s, base=%s)",
                base_price,
                current_item.id,
                current_item.base,
            )
        else:
            # Fallback to product list price
            base_price = product.lst_price
            _logger.warning(
                "[TOTAL MARGIN] Could not find base item, using product.lst_price: %.2f",
                base_price,
            )

        # Reverse margins list since we collected them backwards
        margins.reverse()

        return base_price, margins

    def _compute_original_base_price(
        self, item, product, quantity, uom, date, currency
    ):
        """
        Compute the original base price from a pricelist item without applying
        any margin formula.

        Args:
            item: The pricelist item at the base of the chain
            product: The product to price
            quantity: Quantity
            uom: Unit of measure
            date: Date for pricing
            currency: Target currency

        Returns:
            float: The base price before any margins
        """
        rule_base = item.base or "list_price"

        # Handle custom base from product_sale_price_from_pricelist
        if rule_base == "last_purchase_price":
            src_currency = product.currency_id
            price = product.last_purchase_price_received or 0.0
            _logger.info("[TOTAL MARGIN] Using last_purchase_price: %.2f", price)

        elif rule_base == "standard_price":
            src_currency = product.cost_currency_id
            price = product.standard_price or 0.0
            _logger.info("[TOTAL MARGIN] Using standard_price: %.2f", price)

        elif rule_base == "pricelist" and item.base_pricelist_id:
            # This shouldn't happen if we traversed correctly, but handle it
            _logger.warning(
                "[TOTAL MARGIN] Unexpected pricelist base at bottom of chain"
            )
            src_currency = item.base_pricelist_id.currency_id
            price = item.base_pricelist_id._get_product_price(
                product=product,
                quantity=quantity,
                currency=src_currency,
                uom=uom,
                date=date,
            )

        else:  # list_price (default)
            src_currency = product.currency_id
            price = product.lst_price or 0.0
            _logger.info("[TOTAL MARGIN] Using list_price: %.2f", price)

        # Convert currency if needed
        if src_currency and currency and src_currency != currency:
            company = self.env.company
            price = src_currency._convert(
                price,
                currency,
                company,
                date or fields.Date.today(),
            )
            _logger.info(
                "[TOTAL MARGIN] Converted price from %s to %s: %.2f",
                src_currency.name,
                currency.name,
                price,
            )

        return price

    def _apply_formula_extras(self, price, base_price, currency):
        """
        Apply price_round, price_surcharge, and min/max margins to the calculated price.

        Args:
            price: The price after margin is applied
            base_price: The original base price (for min/max margin calculation)
            currency: Currency for conversions

        Returns:
            float: The final price with all extras applied
        """
        # Rounding
        if self.price_round:
            price = float_round(price, precision_rounding=self.price_round)
            _logger.info("[TOTAL MARGIN] After rounding: %.2f", price)

        # Surcharge
        if self.price_surcharge:
            surcharge = self.price_surcharge
            if self.currency_id and currency and self.currency_id != currency:
                company = self.env.company
                surcharge = self.currency_id._convert(
                    surcharge,
                    currency,
                    company,
                    fields.Date.today(),
                )
            price += surcharge
            _logger.info(
                "[TOTAL MARGIN] After surcharge (+%.2f): %.2f", surcharge, price
            )

        # Min margin
        if self.price_min_margin:
            min_margin = self.price_min_margin
            if self.currency_id and currency and self.currency_id != currency:
                company = self.env.company
                min_margin = self.currency_id._convert(
                    min_margin,
                    currency,
                    company,
                    fields.Date.today(),
                )
            min_price = base_price + min_margin
            if price < min_price:
                _logger.info(
                    "[TOTAL MARGIN] Applying min_margin: %.2f -> %.2f",
                    price,
                    min_price,
                )
                price = min_price

        # Max margin
        if self.price_max_margin:
            max_margin = self.price_max_margin
            if self.currency_id and currency and self.currency_id != currency:
                company = self.env.company
                max_margin = self.currency_id._convert(
                    max_margin,
                    currency,
                    company,
                    fields.Date.today(),
                )
            max_price = base_price + max_margin
            if price > max_price:
                _logger.info(
                    "[TOTAL MARGIN] Applying max_margin: %.2f -> %.2f",
                    price,
                    max_price,
                )
                price = max_price

        return price

    def _apply_global_margin_limits(self, price, base_price):
        """
        Apply global minimum and maximum margin limits from configuration.

        Instead of adjusting the margin percentage, this method checks if the
        calculated price respects the global limits and adjusts the price directly
        if needed. The limits are interpreted according to the global_margin_type
        configuration.

        Args:
            price: The calculated price
            base_price: The base price (cost)

        Returns:
            float: The adjusted price
        """
        # Get global margin limits from configuration
        IrConfigParam = self.env["ir.config_parameter"].sudo()
        min_margin = float(
            IrConfigParam.get_param(
                "product_pricelist_total_margin.min_percent", default="0.0"
            )
        )
        max_margin = float(
            IrConfigParam.get_param(
                "product_pricelist_total_margin.max_percent", default="0.0"
            )
        )
        global_margin_type = IrConfigParam.get_param(
            "product_pricelist_total_margin.global_margin_type", default="markup"
        )

        if min_margin <= 0.0 and max_margin <= 0.0:
            # No limits configured
            return price

        # Calculate min/max prices based on global margin type
        if global_margin_type == "margin":
            # Commercial Margin: PVP = Cost / (1 - margin%)
            if min_margin > 0.0:
                if min_margin >= 100:
                    min_margin = 99.0
                min_price = base_price / (1 - min_margin / 100)
            else:
                min_price = 0.0

            if max_margin > 0.0:
                if max_margin >= 100:
                    max_margin = 99.0
                max_price = base_price / (1 - max_margin / 100)
            else:
                max_price = float("inf")
        else:
            # Markup: PVP = Cost × (1 + markup%)
            min_price = base_price * (1 + min_margin / 100) if min_margin > 0.0 else 0.0
            max_price = (
                base_price * (1 + max_margin / 100)
                if max_margin > 0.0
                else float("inf")
            )

        original_price = price

        # Apply minimum price limit
        if min_margin > 0.0 and price < min_price:
            price = min_price
            _logger.info(
                "[TOTAL MARGIN] Applied global minimum (%s %.2f%%): %.2f -> %.2f",
                global_margin_type,
                min_margin,
                original_price,
                price,
            )

        # Apply maximum price limit
        if max_margin > 0.0 and price > max_price:
            price = max_price
            _logger.info(
                "[TOTAL MARGIN] Applied global maximum (%s %.2f%%): %.2f -> %.2f",
                global_margin_type,
                max_margin,
                original_price,
                price,
            )

        return price

    def _compute_price(
        self,
        product,
        quantity,
        uom,
        date,
        currency=None,
    ):
        """
        Override to implement total margin calculation when use_total_margin is True.

        Instead of compounding margins (applying each margin on top of the previous
        result), this method:
        1. Traverses the pricelist chain to collect all margins
        2. Sums them additively
        3. Applies the total margin to the original base price
        4. Enforces global min/max margin limits if configured

        Example:
            Base price: 100€
            Pricelist 1: -5% discount
            Pricelist 2: 25% markup

            Standard Odoo (compound): 100 * 0.95 * 1.25 = 118.75€
            This module (total): 100 * (1 + 0.20) = 120€
        """
        # Only apply total margin logic if:
        # 1. use_total_margin is True
        # 2. compute_price is 'formula' (not 'fixed' or 'percentage')
        # 3. We're in a pricelist chain (base='pricelist')
        if (
            self.use_total_margin
            and self.compute_price == "formula"
            and self.base == "pricelist"
        ):
            _logger.info(
                "[TOTAL MARGIN] Computing total margin for product %s [%s]",
                product.default_code or product.name,
                product.id,
            )

            # Get base price and all margins in the chain
            base_price, margins = self._get_base_price_and_margins(
                product, quantity, uom, date, currency
            )

            # Sum margins additively
            total_margin = sum(margins)
            _logger.info(
                "[TOTAL MARGIN] Margins: %s = %.2f%% total",
                [f"{m:.2f}%" for m in margins],
                total_margin,
            )

            # Apply total margin to base price using selected margin type
            margin_type = self.margin_type or "markup"
            if margin_type == "margin":
                # Commercial Margin: PVP = Cost / (1 - margin%)
                if total_margin >= 100:
                    _logger.warning(
                        "[TOTAL MARGIN] Commercial margin %.2f%% >= 100%%, capping at 99%%",
                        total_margin,
                    )
                    total_margin = 99.0
                price = base_price / (1 - total_margin / 100)
                _logger.info(
                    "[TOTAL MARGIN] Commercial Margin: Base %.2f / (1 - %.2f%%) = %.2f",
                    base_price,
                    total_margin,
                    price,
                )
            else:
                # Markup: PVP = Cost × (1 + markup%)
                price = base_price * (1 + total_margin / 100)
                _logger.info(
                    "[TOTAL MARGIN] Markup: Base %.2f * (1 + %.2f%%) = %.2f",
                    base_price,
                    total_margin,
                    price,
                )

            # Apply global min/max margin limits (checks and adjusts price)
            price = self._apply_global_margin_limits(price, base_price)

            # Apply formula extras (round, surcharge, min/max margins)
            price = self._apply_formula_extras(price, base_price, currency)

            _logger.info("[TOTAL MARGIN] Final price: %.2f", price)
            return price

        # Standard behavior for all other cases
        return super()._compute_price(product, quantity, uom, date, currency)
