from __future__ import annotations

import asyncio
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, Set, Tuple

from app.streaming.live_store import live_store
from app.services.price_simulator import gold_simulator


@dataclass(frozen=True)
class FeedKey:
    symbol: str
    timeframe: str


class LocalFeedProvider:
    """In-memory price feed backed by the gold price simulator."""

    def __init__(self) -> None:
        self._tasks: Dict[FeedKey, asyncio.Task] = {}
        self._pinned: Set[FeedKey] = set()
        self._lock = asyncio.Lock()
        self._active_counts: Dict[FeedKey, int] = {}

    def get_status(self) -> list[dict]:
        out: list[dict] = []
        for key, task in self._tasks.items():
            hist = live_store.get_history(key.symbol, key.timeframe)
            last_ts = hist[-1]["time"] if hist else None
            last_iso = None
            if isinstance(last_ts, (int, float)):
                try:
                    last_iso = datetime.utcfromtimestamp(int(last_ts)).isoformat() + "Z"
                except Exception:
                    last_iso = None
            out.append(
                {
                    "symbol": key.symbol,
                    "timeframe": key.timeframe,
                    "subscribers": self._active_counts.get(key, 0),
                    "last_event_time": last_iso,
                }
            )
        return out

    async def subscribe(self, symbol: str, timeframe: str = "1m") -> Tuple[asyncio.Queue, Any]:
        if timeframe != "1m":
            raise ValueError("LocalFeedProvider currently supports timeframe '1m' only")
        key = FeedKey(symbol=symbol.upper().replace("/", ""), timeframe=timeframe)
        queue: asyncio.Queue = asyncio.Queue(maxsize=100)
        live_store.subscribe(key.symbol, key.timeframe, queue)
        async with self._lock:
            self._active_counts[key] = self._active_counts.get(key, 0) + 1
            if key not in self._tasks:
                self._tasks[key] = asyncio.create_task(self._run_simulator_poller(key))

        async def _unsubscribe() -> None:
            live_store.unsubscribe(key.symbol, key.timeframe, queue)
            async with self._lock:
                self._active_counts[key] = max(0, self._active_counts.get(key, 0) - 1)
                if self._active_counts.get(key, 0) == 0 and key not in self._pinned:
                    task = self._tasks.pop(key, None)
                    if task:
                        task.cancel()
                    self._active_counts.pop(key, None)

        return queue, _unsubscribe

    async def ensure_stream(self, symbol: str, timeframe: str = "1m") -> None:
        if timeframe != "1m":
            raise ValueError("LocalFeedProvider currently supports timeframe '1m' only")
        key = FeedKey(symbol=symbol.upper().replace("/", ""), timeframe=timeframe)
        async with self._lock:
            self._pinned.add(key)
            if key not in self._tasks:
                self._tasks[key] = asyncio.create_task(self._run_simulator_poller(key))

    async def release_stream(self, symbol: str, timeframe: str = "1m") -> None:
        key = FeedKey(symbol=symbol.upper().replace("/", ""), timeframe=timeframe)
        async with self._lock:
            self._pinned.discard(key)
            if self._active_counts.get(key, 0) == 0:
                task = self._tasks.pop(key, None)
                if task:
                    task.cancel()

    async def _run_simulator_poller(self, key: FeedKey) -> None:
        symbol = key.symbol
        simulator = gold_simulator
        if simulator.current_price < 2400 or simulator.current_price > 2900:
            simulator.current_price = 2650.0
            simulator.base_price = 2650.0

        last_ts: int | None = None
        poll_interval = 3

        while True:
            try:
                candle = simulator.get_live_candle(interval="1min")
                tsec = candle.time

                if last_ts is None or tsec > last_ts:
                    dt = datetime.utcfromtimestamp(tsec)
                    evt = {
                        "symbol": symbol,
                        "timeframe": key.timeframe,
                        "open_time": dt.isoformat(),
                        "close_time": dt.isoformat(),
                        "open": candle.open,
                        "high": candle.high,
                        "low": candle.low,
                        "close": candle.close,
                        "volume": candle.volume or 0.0,
                        "is_closed": True,
                        "source": "local_simulator",
                    }
                    try:
                        live_store.ingest_bar(
                            symbol=symbol,
                            timeframe="1m",
                            bar={
                                "time": tsec,
                                "open": evt["open"],
                                "high": evt["high"],
                                "low": evt["low"],
                                "close": evt["close"],
                                "volume": evt["volume"],
                            },
                        )
                    except Exception:
                        pass

                    last_ts = tsec

                await asyncio.sleep(poll_interval)
            except asyncio.CancelledError:
                break
            except Exception:
                await asyncio.sleep(poll_interval)


local_feed = LocalFeedProvider()
