"""Holding and analytics types."""

from __future__ import annotations

from dataclasses import dataclass, field
from decimal import Decimal

from fuggers_py.bonds.traits import Bond
from fuggers_py.bonds.types import RatingInfo, SectorInfo, SeniorityInfo
from fuggers_py.core.types import Currency, Price

from .classification import Classification


def _to_decimal(value: object) -> Decimal:
    if isinstance(value, Decimal):
        return value
    if isinstance(value, Price):
        return value.as_percentage()
    return Decimal(str(value))


@dataclass(frozen=True, slots=True)
class HoldingAnalytics:
    name: str
    market_value: Decimal
    dirty_value: Decimal
    clean_value: Decimal
    accrued_value: Decimal
    duration: Decimal
    convexity: Decimal
    dv01: Decimal
    ytm: Decimal | None = None
    ytw: Decimal | None = None
    ytc: Decimal | None = None
    current_yield: Decimal | None = None
    best_yield: Decimal | None = None
    z_spread: Decimal | None = None
    oas: Decimal | None = None
    g_spread: Decimal | None = None
    i_spread: Decimal | None = None
    asw: Decimal | None = None
    best_spread: Decimal | None = None
    spread_duration: Decimal | None = None
    cs01: Decimal | None = None
    modified_duration: Decimal | None = None
    effective_duration: Decimal | None = None
    macaulay_duration: Decimal | None = None
    effective_convexity: Decimal | None = None
    key_rate_profile: dict[str, Decimal] = field(default_factory=dict)
    liquidity_score: Decimal | None = None
    weighted_average_life: Decimal | None = None
    coupon: Decimal | None = None


@dataclass(frozen=True, slots=True)
class Holding:
    instrument: Bond
    quantity: Decimal = Decimal(1)
    clean_price: Price | Decimal | None = None
    market_value: Decimal | None = None
    accrued_interest: Decimal | None = None
    analytics: HoldingAnalytics | None = None
    label: str | None = None
    id: str | None = None
    classification: Classification | None = None
    rating_info: RatingInfo | None = None
    sector_info: SectorInfo | None = None
    seniority_info: SeniorityInfo | None = None
    liquidity_score: Decimal | None = None
    custom_fields: dict[str, str] = field(default_factory=dict)
    fx_rate: Decimal = Decimal(1)

    def __post_init__(self) -> None:
        object.__setattr__(self, "quantity", _to_decimal(self.quantity))
        if self.market_value is not None:
            object.__setattr__(self, "market_value", _to_decimal(self.market_value))
        if self.accrued_interest is not None:
            object.__setattr__(self, "accrued_interest", _to_decimal(self.accrued_interest))
        if self.liquidity_score is not None:
            object.__setattr__(self, "liquidity_score", _to_decimal(self.liquidity_score))
        object.__setattr__(self, "fx_rate", _to_decimal(self.fx_rate))

    @property
    def par_amount(self) -> Decimal:
        return self.quantity

    @property
    def market_price(self) -> Decimal | None:
        if self.clean_price is None:
            return None
        return _to_decimal(self.clean_price)

    @property
    def currency(self) -> Currency:
        if self.classification is not None and self.classification.currency is not None:
            return self.classification.currency
        return self.instrument.currency()

    def name(self) -> str:
        if self.label is not None:
            return self.label
        if self.id is not None:
            return self.id
        identifiers = self.instrument.identifiers()
        return identifiers.isin.value if identifiers.isin is not None else self.instrument.__class__.__name__

    @property
    def market_value_amount(self) -> Decimal:
        if self.market_value is not None:
            return self.market_value
        if self.clean_price is None:
            return Decimal(0)
        return _to_decimal(self.clean_price) * self.quantity

    @property
    def dirty_market_value(self) -> Decimal:
        accrued = self.accrued_interest or Decimal(0)
        return self.market_value_amount + (accrued * self.quantity)

    @property
    def base_currency_value(self) -> Decimal:
        return self.market_value_amount * self.fx_rate

    def weight_in_portfolio(self, total_market_value: Decimal) -> Decimal:
        if total_market_value == 0:
            return Decimal(0)
        return self.market_value_amount / total_market_value


@dataclass(slots=True)
class HoldingBuilder:
    instrument: Bond | None = None
    quantity: Decimal = Decimal(1)
    clean_price: Price | Decimal | None = None
    market_value: Decimal | None = None
    accrued_interest: Decimal | None = None
    analytics: HoldingAnalytics | None = None
    label: str | None = None
    id: str | None = None
    classification: Classification | None = None
    rating_info: RatingInfo | None = None
    sector_info: SectorInfo | None = None
    seniority_info: SeniorityInfo | None = None
    liquidity_score: Decimal | None = None
    custom_fields: dict[str, str] = field(default_factory=dict)
    fx_rate: Decimal = Decimal(1)

    def with_instrument(self, instrument: Bond) -> "HoldingBuilder":
        self.instrument = instrument
        return self

    def with_quantity(self, quantity: object) -> "HoldingBuilder":
        self.quantity = _to_decimal(quantity)
        return self

    def with_par_amount(self, par_amount: object) -> "HoldingBuilder":
        return self.with_quantity(par_amount)

    def with_clean_price(self, clean_price: Price | Decimal) -> "HoldingBuilder":
        self.clean_price = clean_price
        return self

    def with_market_price(self, market_price: Price | Decimal) -> "HoldingBuilder":
        return self.with_clean_price(market_price)

    def with_market_value(self, market_value: object) -> "HoldingBuilder":
        self.market_value = _to_decimal(market_value)
        return self

    def with_accrued_interest(self, accrued_interest: object) -> "HoldingBuilder":
        self.accrued_interest = _to_decimal(accrued_interest)
        return self

    def with_analytics(self, analytics: HoldingAnalytics) -> "HoldingBuilder":
        self.analytics = analytics
        return self

    def with_label(self, label: str) -> "HoldingBuilder":
        self.label = label
        return self

    def with_id(self, value: str) -> "HoldingBuilder":
        self.id = value
        return self

    def with_classification(self, classification: Classification) -> "HoldingBuilder":
        self.classification = classification
        return self

    def with_rating_info(self, rating_info: RatingInfo) -> "HoldingBuilder":
        self.rating_info = rating_info
        return self

    def with_sector_info(self, sector_info: SectorInfo) -> "HoldingBuilder":
        self.sector_info = sector_info
        return self

    def with_seniority_info(self, seniority_info: SeniorityInfo) -> "HoldingBuilder":
        self.seniority_info = seniority_info
        return self

    def with_liquidity_score(self, liquidity_score: object) -> "HoldingBuilder":
        self.liquidity_score = _to_decimal(liquidity_score)
        return self

    def with_fx_rate(self, fx_rate: object) -> "HoldingBuilder":
        self.fx_rate = _to_decimal(fx_rate)
        return self

    def build(self) -> Holding:
        if self.instrument is None:
            raise ValueError("HoldingBuilder requires an instrument.")
        return Holding(
            instrument=self.instrument,
            quantity=self.quantity,
            clean_price=self.clean_price,
            market_value=self.market_value,
            accrued_interest=self.accrued_interest,
            analytics=self.analytics,
            label=self.label,
            id=self.id,
            classification=self.classification,
            rating_info=self.rating_info,
            sector_info=self.sector_info,
            seniority_info=self.seniority_info,
            liquidity_score=self.liquidity_score,
            custom_fields=dict(self.custom_fields),
            fx_rate=self.fx_rate,
        )


Position = Holding
PositionAnalytics = HoldingAnalytics


__all__ = [
    "Holding",
    "HoldingAnalytics",
    "HoldingBuilder",
    "Position",
    "PositionAnalytics",
]
