"""Par-par asset-swap spread helpers.

The returned spread is a raw decimal par-par ASW spread derived from a dirty
price input and the curve annuity.
"""

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal

from fuggers_py.bonds.instruments import FixedBond
from fuggers_py.core.traits import YieldCurve
from fuggers_py.core.types import Date


@dataclass(frozen=True, slots=True)
class ParParAssetSwap:
    """Par-par asset-swap spread calculator."""

    curve: YieldCurve

    def calculate(self, bond: FixedBond, dirty_price: object, settlement_date: Date) -> Decimal:
        """Return the par-par asset-swap spread as a raw decimal."""
        annuity = self._annuity(bond, settlement_date)
        if annuity == 0:
            return Decimal(0)
        swap_rate = self._swap_rate(bond, settlement_date)
        price = Decimal(str(dirty_price))
        return ((Decimal(100) - price) / annuity) + bond.coupon_rate() - swap_rate

    def _swap_rate(self, bond: FixedBond, settlement_date: Date) -> Decimal:
        start_df = self.curve.discount_factor(settlement_date)
        end_df = self.curve.discount_factor(bond.maturity_date())
        annuity = self._annuity(bond, settlement_date)
        if annuity == 0:
            return Decimal(0)
        return (start_df - end_df) / annuity

    def _annuity(self, bond: FixedBond, settlement_date: Date) -> Decimal:
        day_count = bond.rules().accrual_day_count_obj()
        annuity = Decimal(0)
        for cf in bond.cash_flows(settlement_date):
            if not cf.is_coupon():
                continue
            accrual_start = cf.accrual_start or settlement_date
            accrual_end = cf.accrual_end or cf.date
            tau = day_count.year_fraction(accrual_start, accrual_end)
            annuity += tau * self.curve.discount_factor(cf.date) / self.curve.discount_factor(settlement_date)
        return annuity


__all__ = ["ParParAssetSwap"]
