"""GHO variable debt token processor for revision 4.

Revision 4 deprecates the discount mechanism and uses standard rayDiv
(rounding half up) like earlier revisions. Floor division was added
in revision 5.
"""

import typing

import degenbot.aave.libraries
from degenbot.aave.processors.base import (
    DebtBurnEvent,
    DebtMintEvent,
    GhoDebtTokenProcessor,
    GhoScaledTokenBurnResult,
    GhoScaledTokenMintResult,
    GhoUserOperation,
    MathLibraries,
)


class GhoV4Processor(GhoDebtTokenProcessor):
    """Processor for GHO VariableDebtToken revision 4.

    Revision 4 has the discount mechanism deprecated and uses standard
    rayDiv (rounding half up). Floor division was added in revision 5.
    """

    revision = 4

    def __init__(self) -> None:
        self._math_libs = MathLibraries(
            wad_ray=degenbot.aave.libraries.wad_ray_math,
            percentage=degenbot.aave.libraries.percentage_math,
        )

    def get_math_libraries(self) -> MathLibraries:
        """Get the math libraries for this revision."""
        return self._math_libs

    def supports_discount(self) -> bool:  # noqa: PLR6301
        """Revision 4+ does not support discount mechanism."""
        return False

    def calculate_mint_scaled_amount(self, raw_amount: int, index: int) -> int:
        """Calculate scaled amount from raw underlying amount for mint operations.

        GHO revision 4 uses standard rayDiv (rounding half up) since the discount
        mechanism was deprecated. The _mintScaled function in the revision 4
        contract calls amount.rayDiv(index) without any special rounding.

        This method should be called before process_mint_event() to pre-calculate
        the scaled_delta parameter from the original borrow amount, ensuring
        consistent rounding with on-chain calculations.

        Args:
            raw_amount: The raw underlying amount (unscaled)
            index: The variable debt index

        Returns:
            The scaled amount (amountScaled)
        """
        return self._math_libs["wad_ray"].ray_div(
            a=raw_amount,
            b=index,
        )

    def process_mint_event(
        self,
        event_data: DebtMintEvent,
        previous_balance: int,
        previous_index: int,
        previous_discount: int,  # noqa: ARG002
        actual_repay_amount: int | None = None,
    ) -> GhoScaledTokenMintResult:
        """Process a GHO debt mint event without discount.

        For accurate balance tracking, the scaled_delta parameter should be
        pre-calculated using calculate_mint_scaled_amount() from the original
        borrow amount (extracted from the BORROW event). This ensures the
        rounding matches the on-chain Pool contract calculation.

        Args:
            event_data: The mint event data from the token contract
            previous_balance: The user's scaled balance before this event
            previous_index: The index at previous_balance calculation
            previous_discount: Ignored (no discount in rev 4+)
            actual_repay_amount: Optional actual repay amount from Repay event.
                When provided, use this instead of deriving from Mint event fields.
                This avoids 1 wei rounding errors from integer truncation.

        Returns:
            GhoMintResult with balance_delta, new_index, user_operation,
            discount_scaled=0, and should_refresh_discount=False
        """
        wad_ray_math = self._math_libs["wad_ray"]

        if event_data.value >= event_data.balance_increase:
            # GHO BORROW: emitted in _mintScaled
            #
            # Revision 4 uses standard rayDiv (rounding half up) via _mintScaled.
            # The contract calls amount.rayDiv(index) without floor division.
            #
            # When processing BORROW events, the scaled_delta should be pre-calculated
            # using calculate_mint_scaled_amount() from the original borrow amount
            # extracted from the BORROW event, not derived from the Mint event values.
            requested_amount = event_data.value - event_data.balance_increase
            balance_delta = wad_ray_math.ray_div(
                a=requested_amount,
                b=event_data.index,
            )
            user_operation = GhoUserOperation.GHO_BORROW

        elif event_data.balance_increase > event_data.value:
            # GHO REPAY: emitted in _burnScaled
            # The Mint event is emitted when interest > repayment amount.
            # The net balance change is just burning the scaled repayment amount.
            # Use actual_repay_amount from Repay event if provided to avoid
            # 1 wei rounding errors from deriving from Mint event fields.
            # See debug/aave/0037 and 0038 for details.
            if actual_repay_amount is not None:
                amount_repaid = actual_repay_amount
            else:
                # Fallback: derive from Mint event (may have 1 wei rounding error)
                amount_repaid = event_data.balance_increase - event_data.value

            balance_delta = -wad_ray_math.ray_div(
                a=amount_repaid,
                b=event_data.index,
            )
            user_operation = GhoUserOperation.GHO_REPAY

        else:
            # Pure interest accrual (value == balance_increase)
            balance_increase = wad_ray_math.ray_mul(
                a=previous_balance,
                b=event_data.index,
            ) - wad_ray_math.ray_mul(
                a=previous_balance,
                b=previous_index,
            )

            # Convert back to scaled
            balance_increase_scaled = wad_ray_math.ray_div(
                a=balance_increase,
                b=event_data.index,
            )

            balance_delta = balance_increase_scaled
            user_operation = GhoUserOperation.GHO_INTEREST_ACCRUAL

        # Revision 4+ never refreshes discount (discount is deprecated)
        should_refresh_discount = False

        return GhoScaledTokenMintResult(
            balance_delta=balance_delta,
            new_index=event_data.index,
            user_operation=user_operation,
            discount_scaled=0,
            should_refresh_discount=should_refresh_discount,
        )

    def process_burn_event(
        self,
        event_data: DebtBurnEvent,
        previous_balance: int,  # noqa: ARG002
        previous_index: int,  # noqa: ARG002
        previous_discount: int,  # noqa: ARG002
    ) -> GhoScaledTokenBurnResult:
        """Process a GHO debt burn event without discount.

        Args:
            event_data: The burn event data
            previous_balance: The user's balance before this event
            previous_index: The index at previous_balance calculation
            previous_discount: Ignored (no discount in rev 4+)

        Returns:
            GhoBurnResult with balance_delta, new_index, discount_scaled=0,
            and should_refresh_discount=False
        """
        wad_ray_math = self._math_libs["wad_ray"]

        # uint256 amountToBurn = amount - balanceIncrease
        requested_amount = event_data.value + event_data.balance_increase

        # uint256 amountScaled = amount.rayDiv(index)
        # Revision 4 uses standard rayDiv (rounding half up) via _burnScaled.
        # No discount in rev 4.
        balance_delta = -wad_ray_math.ray_div(
            a=requested_amount,
            b=event_data.index,
        )

        # Revision 4+ never refreshes discount (discount is deprecated)
        should_refresh_discount = False

        return GhoScaledTokenBurnResult(
            balance_delta=balance_delta,
            new_index=event_data.index,
            discount_scaled=0,
            should_refresh_discount=should_refresh_discount,
        )

    def get_discounted_balance(
        self,
        scaled_balance: int,
        previous_index: int,  # noqa: ARG002
        current_index: int,
        discount_percent: int,  # noqa: ARG002
    ) -> int:
        """Calculate balance without discount.

        In revision 4+, this simply returns rayMul(scaled_balance, current_index).

        Args:
            scaled_balance: The scaled balance
            previous_index: Ignored
            current_index: The current debt index
            discount_percent: Ignored (no discount in rev 4+)

        Returns:
            The balance without discount
        """
        wad_ray_math = self._math_libs["wad_ray"]

        if scaled_balance == 0:
            return 0

        return wad_ray_math.ray_mul(
            a=scaled_balance,
            b=current_index,
        )

    @typing.override
    def accrue_debt_on_action(
        self,
        previous_scaled_balance: int,
        previous_index: int,
        discount_percent: int,
        current_index: int,
    ) -> int:
        """Calculate debt accrual without discount.

        In revision 4+, the discount mechanism is deprecated, so this
        always returns 0.

        Args:
            previous_scaled_balance: Ignored
            previous_index: Ignored
            discount_percent: Ignored (no discount in rev 4+)
            current_index: Ignored

        Returns:
            Always returns 0
        """
        return 0
