from __future__ import annotations

import logging
import time
from typing import Callable, Awaitable, Optional, Sequence

from app.schemas.schemas import PositionMetrics, PatternSignal
from app.services.metals.bullionvault_service import get_bullionvault_gold_price
from app.services.metals.gold_price_fetcher import gold_price_fetcher

logger = logging.getLogger(__name__)


class PriceAnchorService:
    """Rescales simulated metric snapshots to the live gold price feed."""

    def __init__(self, ttl_seconds: int = 30) -> None:
        self._ttl = ttl_seconds
        self._cache_price: Optional[float] = None
        self._cache_ts: float = 0.0

    async def get_anchor_price(self, symbol: str = "XAUUSD") -> Optional[float]:
        now = time.time()
        if self._cache_price and (now - self._cache_ts) < self._ttl:
            return self._cache_price

        fetchers: Sequence[Callable[[], Awaitable[Optional[float]]]] = (
            self._get_bullionvault_price,
            self._get_fallback_price,
        )
        for fetch in fetchers:
            try:
                price = await fetch()
            except Exception as exc:  # pragma: no cover - best effort logging only
                logger.warning("Price anchor fetch failed: %s", exc)
                continue
            if price and price > 0:
                self._cache_price = float(price)
                self._cache_ts = now
                return self._cache_price
        return self._cache_price
    
    def get_anchor_price_sync(self, symbol: str = "XAUUSD") -> Optional[float]:
        """Synchronous version that returns cached price only"""
        now = time.time()
        if self._cache_price and (now - self._cache_ts) < self._ttl:
            return self._cache_price
        return self._cache_price

    async def _get_bullionvault_price(self) -> Optional[float]:
        data = await get_bullionvault_gold_price("USD")
        return float(data["price"]) if data and data.get("price") else None

    async def _get_fallback_price(self) -> Optional[float]:
        data = await gold_price_fetcher.get_current_gold_price()
        return float(data["price"]) if data and data.get("price") else None

    def apply_anchor(self, metrics: PositionMetrics, anchor_price: Optional[float]) -> PositionMetrics:
        if not anchor_price or metrics.current_price <= 0:
            return metrics

        scale = anchor_price / metrics.current_price
        if abs(scale - 1.0) < 0.005:
            # Already close enough to the anchor, skip unnecessary work
            return metrics

        if not 0.2 <= scale <= 5:
            logger.warning("Skipping unrealistic price anchor scaling (scale=%.4f)", scale)
            return metrics

        scaled = metrics.model_copy(deep=True)

        def scale_value(value: Optional[float], decimals: int = 4) -> Optional[float]:
            if value is None:
                return None
            return round(value * scale, decimals)

        def scale_list(values: list[float]) -> list[float]:
            return [round(v * scale, 2) for v in values]

        scaled.current_price = round(anchor_price, 2)
        scaled.previous_close = scale_value(scaled.previous_close, 2)
        scaled.high = scale_value(scaled.high, 2)
        scaled.low = scale_value(scaled.low, 2)
        scaled.atr14 = scale_value(scaled.atr14)
        scaled.ema21 = scale_value(scaled.ema21)
        scaled.sma55 = scale_value(scaled.sma55)
        scaled.sma100 = scale_value(scaled.sma100)
        scaled.sma200 = scale_value(scaled.sma200)
        scaled.bb_basis = scale_value(scaled.bb_basis)
        scaled.bb_upper = scale_value(scaled.bb_upper)
        scaled.bb_lower = scale_value(scaled.bb_lower)
        scaled.zlsma = scale_value(scaled.zlsma)
        scaled.chandelier_long_stop = scale_value(scaled.chandelier_long_stop, 2)
        scaled.chandelier_short_stop = scale_value(scaled.chandelier_short_stop, 2)
        scaled.momentum12 = scale_value(scaled.momentum12)
        scaled.support_levels = scale_list(scaled.support_levels)
        scaled.resistance_levels = scale_list(scaled.resistance_levels)

        scaled.pattern_signals = [
            signal.model_copy(update={"price": scale_value(signal.price, 2)})
            for signal in scaled.pattern_signals
        ]

        if scaled.previous_close is not None:
            scaled.change = round(scaled.current_price - scaled.previous_close, 4)
            if scaled.previous_close:
                scaled.change_percent = round((scaled.change / scaled.previous_close) * 100, 4)
        else:
            scaled.change = scale_value(scaled.change)
            if scaled.previous_close:
                scaled.change_percent = round((scaled.change or 0.0) / scaled.previous_close * 100, 4)

        return scaled


price_anchor_service = PriceAnchorService()
