"""Wrapper around ``mcp-macro`` (``docs/04-mcp-integration.md``).

Exposes a single use case relevant to Cerbero Bite: how many days
separate the current moment from the next high-severity macro event in
the requested window. The orchestrator feeds the result straight into
``entry_validator.EntryContext.next_macro_event_in_days``.
"""

from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal
from typing import Any

from pydantic import BaseModel, ConfigDict

from cerbero_bite.clients._base import HttpToolClient
from cerbero_bite.clients._exceptions import McpDataAnomalyError

__all__ = ["MacroClient", "MacroEvent"]


class MacroEvent(BaseModel):
    """One row of the macro calendar."""

    model_config = ConfigDict(frozen=True, extra="ignore")

    name: str
    country_code: str
    importance: str
    datetime_utc: datetime | None


class MacroClient:
    """High-level wrapper that returns typed macro events."""

    SERVICE = "macro"

    def __init__(self, http: HttpToolClient) -> None:
        if http.service != self.SERVICE:
            raise ValueError(
                f"MacroClient requires service '{self.SERVICE}', got '{http.service}'"
            )
        self._http = http

    async def get_calendar(
        self,
        *,
        days: int,
        country_filter: list[str] | None = None,
        importance_min: str | None = None,
    ) -> list[MacroEvent]:
        """Return the events in the next ``days`` matching the filters."""
        body: dict[str, Any] = {"days": days}
        if country_filter is not None:
            body["country_filter"] = country_filter
        if importance_min is not None:
            body["importance_min"] = importance_min
        raw = await self._http.call("get_macro_calendar", body)
        events = raw.get("events") or []
        out: list[MacroEvent] = []
        for entry in events:
            if not isinstance(entry, dict):
                continue
            out.append(
                MacroEvent(
                    name=str(entry.get("name") or entry.get("event") or ""),
                    country_code=str(entry.get("country_code") or ""),
                    importance=str(entry.get("importance") or "medium"),
                    datetime_utc=_parse_dt(entry.get("datetime_utc"))
                    or _parse_dt(entry.get("date")),
                )
            )
        return out

    async def get_asset_price(self, ticker: str) -> Decimal:
        """Return the latest cross-asset price for ``ticker`` (e.g. ``EURUSD``)."""
        raw = await self._http.call("get_asset_price", {"ticker": ticker})
        if not isinstance(raw, dict):
            raise McpDataAnomalyError(
                f"macro get_asset_price unexpected shape: {type(raw).__name__}",
                service=self.SERVICE,
                tool="get_asset_price",
            )
        if raw.get("error"):
            raise McpDataAnomalyError(
                f"macro get_asset_price error for {ticker}: {raw['error']}",
                service=self.SERVICE,
                tool="get_asset_price",
            )
        price = raw.get("price")
        if price is None:
            raise McpDataAnomalyError(
                f"macro get_asset_price missing 'price' for {ticker}",
                service=self.SERVICE,
                tool="get_asset_price",
            )
        return Decimal(str(price))

    async def eur_usd_rate(self) -> Decimal:
        """Return EUR→USD spot rate (i.e. ``EURUSD`` price)."""
        return await self.get_asset_price("EURUSD")

    async def next_high_severity_within(
        self,
        *,
        days: int,
        countries: list[str] | None = None,
        now: datetime | None = None,
    ) -> int | None:
        """Days until the first high-severity event within ``days``, else None.

        ``now`` is taken from the parameter (default: now in UTC) so the
        decision is reproducible in tests; the result is rounded up to
        whole days because the strategy filter compares with DTE in
        days.
        """
        events = await self.get_calendar(
            days=days,
            country_filter=countries,
            importance_min="high",
        )
        reference = (now or datetime.now(UTC)).astimezone(UTC)
        deltas: list[int] = []
        for event in events:
            if event.datetime_utc is None:
                continue
            delta = event.datetime_utc - reference
            seconds = delta.total_seconds()
            if seconds < 0:
                continue
            deltas.append(int(seconds // 86400))
        return min(deltas) if deltas else None


def _parse_dt(value: Any) -> datetime | None:
    if not isinstance(value, str) or not value:
        return None
    try:
        out = datetime.fromisoformat(value.replace("Z", "+00:00"))
    except ValueError:
        return None
    if out.tzinfo is None:
        out = out.replace(tzinfo=UTC)
    return out
