"""Single-ticker advisor service.

Runs the PEAD and ORB single-ticker adapters concurrently for one
(ticker, asof, optional held position) request and returns a unified
verdict pair.
"""
from __future__ import annotations

import asyncio
import datetime as dt
from datetime import datetime
from pathlib import Path
from typing import Any, Literal
from zoneinfo import ZoneInfo

from pydantic import BaseModel, Field

from libs.advisor.orb_adapter import OrbVerdict, evaluate_orb_buy, evaluate_orb_sell
from libs.advisor.pead_adapter import PeadVerdict, evaluate_pead_buy, evaluate_pead_sell
from libs.common.logging import get_logger

logger = get_logger(__name__)
_ET = ZoneInfo("America/New_York")

PEAD_CHAMPION_CONFIG = "configs/experiments/return_max_long_v7.364_composed_gld.json"
ORB_CHAMPION_CONFIG = "configs/intraday/strategies/orb_gainers_v49.yaml"


class HeldPosition(BaseModel):
    strategy: Literal["pead", "orb"]
    entry_date: dt.date
    entry_price: float


class AdvisorRequest(BaseModel):
    ticker: str
    asof: dt.date | None = None
    held: HeldPosition | None = None


class StrategyVerdict(BaseModel):
    signal: Literal["BUY", "SELL", "HOLD", "NO_SIGNAL", "ERROR"]
    score: float | None = None
    reason: str
    details: dict[str, Any] = Field(default_factory=dict)
    warning: str | None = None


class AdvisorResponse(BaseModel):
    ticker: str
    asof: dt.date
    evaluated_at: str
    pead: StrategyVerdict
    orb: StrategyVerdict


def _today_et() -> dt.date:
    return datetime.now(_ET).date()


def _now_et_iso() -> str:
    return datetime.now(_ET).isoformat()


def _to_strategy_verdict(v: PeadVerdict | OrbVerdict | BaseException) -> StrategyVerdict:
    if isinstance(v, BaseException):
        return StrategyVerdict(
            signal="ERROR",
            reason=f"adapter raised: {type(v).__name__}: {v}",
        )
    return StrategyVerdict(
        signal=v.signal,
        score=v.score,
        reason=v.reason,
        details=v.details,
        warning=v.warning,
    )


async def _run_pead(req: AdvisorRequest, asof: dt.date) -> PeadVerdict:
    if req.held and req.held.strategy == "pead":
        return await evaluate_pead_sell(
            req.ticker, req.held.entry_date, req.held.entry_price,
            asof, PEAD_CHAMPION_CONFIG,
        )
    return await evaluate_pead_buy(req.ticker, asof, PEAD_CHAMPION_CONFIG)


async def _run_orb(req: AdvisorRequest, asof: dt.date) -> OrbVerdict:
    if req.held and req.held.strategy == "orb":
        return await evaluate_orb_sell(
            req.ticker, req.held.entry_date, asof, ORB_CHAMPION_CONFIG,
        )
    return await evaluate_orb_buy(req.ticker, asof, ORB_CHAMPION_CONFIG)


async def evaluate(req: AdvisorRequest) -> AdvisorResponse:
    asof = req.asof or _today_et()
    pead_task = asyncio.create_task(_run_pead(req, asof))
    orb_task = asyncio.create_task(_run_orb(req, asof))
    pead_result, orb_result = await asyncio.gather(
        pead_task, orb_task, return_exceptions=True
    )
    if isinstance(pead_result, BaseException):
        logger.warning("advisor_pead_failed", ticker=req.ticker, error=str(pead_result))
    if isinstance(orb_result, BaseException):
        logger.warning("advisor_orb_failed", ticker=req.ticker, error=str(orb_result))
    return AdvisorResponse(
        ticker=req.ticker.upper(),
        asof=asof,
        evaluated_at=_now_et_iso(),
        pead=_to_strategy_verdict(pead_result),
        orb=_to_strategy_verdict(orb_result),
    )


def champions() -> dict[str, str]:
    return {"pead": PEAD_CHAMPION_CONFIG, "orb": ORB_CHAMPION_CONFIG}


def champion_paths_exist() -> dict[str, bool]:
    return {
        "pead": Path(PEAD_CHAMPION_CONFIG).exists(),
        "orb": Path(ORB_CHAMPION_CONFIG).exists(),
    }
