from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from typing import Literal

PORTFOLIO_HOLDING_ID = "candidate_portfolio_holding_advisor"

AdvisorAction = Literal["advisor_accumulate_watch", "advisor_hold_watch"]
AdvisorStatus = Literal["advisor_watchlist", "cash_or_wait"]


@dataclass(frozen=True)
class HoldingCandidate:
    coin: str
    price: Decimal
    sma_fast: Decimal
    sma_slow: Decimal
    momentum_30d_pct: Decimal
    volume_24h_usd: Decimal
    volatility_30d_pct: Decimal
    correlation_to_btc: Decimal
    data_quality_allowed: bool = True


@dataclass(frozen=True)
class HoldingAllocation:
    coin: str
    target_weight_pct: Decimal
    target_notional_usdc: Decimal
    score: Decimal
    action: AdvisorAction
    reason: str


@dataclass(frozen=True)
class PortfolioHoldingAdvice:
    strategy_id: str
    status: AdvisorStatus
    allocations: list[HoldingAllocation]
    rejected: dict[str, list[str]]
    cash_weight_pct: Decimal
    live_order_allowed: bool = False
    mainnet_signed_action: bool = False


def _q(value: Decimal, places: str = "0.01") -> Decimal:
    return value.quantize(Decimal(places), rounding=ROUND_HALF_UP)


def portfolio_holding_blockers(candidate: HoldingCandidate) -> list[str]:
    blockers: list[str] = []
    if not candidate.data_quality_allowed:
        blockers.append("data_quality")
    if candidate.volume_24h_usd < Decimal("50000000"):
        blockers.append("weak_liquidity")
    if not (candidate.price > candidate.sma_fast > candidate.sma_slow):
        blockers.append("trend_not_confirmed")
    if candidate.momentum_30d_pct < Decimal("3.0"):
        blockers.append("momentum_too_weak")
    if candidate.volatility_30d_pct > Decimal("3.0"):
        blockers.append("volatility_too_high")
    if candidate.correlation_to_btc > Decimal("0.85"):
        blockers.append("too_correlated_to_btc")
    return blockers


def _score(candidate: HoldingCandidate) -> Decimal:
    momentum = min(candidate.momentum_30d_pct, Decimal("25"))
    liquidity_bonus = min(candidate.volume_24h_usd / Decimal("100000000"), Decimal("2"))
    volatility_penalty = candidate.volatility_30d_pct * Decimal("1.2")
    correlation_penalty = candidate.correlation_to_btc * Decimal("2.0")
    return _q(momentum + liquidity_bonus - volatility_penalty - correlation_penalty, "0.0001")


def build_portfolio_holding_advice(
    candidates: list[HoldingCandidate],
    *,
    equity_usdc: Decimal,
    max_assets: int = 4,
    max_weight_pct: Decimal = Decimal("20.00"),
) -> PortfolioHoldingAdvice:
    """Build advisor-only medium/long holding recommendations.

    This is deliberately side-effect-free: it never authorizes orders. It creates
    a measurable sleeve for weekly/monthly allocation research and later manual
    review against buy-and-hold benchmarks.
    """
    rejected: dict[str, list[str]] = {}
    eligible: list[tuple[Decimal, HoldingCandidate]] = []
    for candidate in candidates:
        blockers = portfolio_holding_blockers(candidate)
        coin = candidate.coin.upper()
        if blockers:
            rejected[coin] = blockers
            continue
        eligible.append((_score(candidate), candidate))

    eligible.sort(key=lambda item: (item[0], item[1].coin.upper()), reverse=True)
    allocations: list[HoldingAllocation] = []
    for score, candidate in eligible[: max(0, max_assets)]:
        weight = max_weight_pct
        notional = _q(equity_usdc * weight / Decimal("100"))
        action: AdvisorAction = "advisor_accumulate_watch" if candidate.momentum_30d_pct >= Decimal("6.0") else "advisor_hold_watch"
        allocations.append(
            HoldingAllocation(
                coin=candidate.coin.upper(),
                target_weight_pct=weight,
                target_notional_usdc=notional,
                score=score,
                action=action,
                reason="weekly_trend_momentum_liquidity_screen",
            )
        )

    invested_weight = sum((item.target_weight_pct for item in allocations), Decimal("0"))
    cash_weight = max(Decimal("0"), Decimal("100.00") - invested_weight)
    return PortfolioHoldingAdvice(
        strategy_id=PORTFOLIO_HOLDING_ID,
        status="advisor_watchlist" if allocations else "cash_or_wait",
        allocations=allocations,
        rejected=rejected,
        cash_weight_pct=_q(cash_weight),
        live_order_allowed=False,
        mainnet_signed_action=False,
    )
