"""Weather source runtime manager — polls APIs and caches WeatherData.

Ref-counted pool: multiple CSS streams sharing the same weather source
share one polling loop.  Lazy-creates runtimes on first acquire().
"""

import threading
import time
from typing import Dict, Optional

from wled_controller.core.weather.weather_provider import (
    DEFAULT_WEATHER,
    WeatherData,
    WeatherProvider,
    create_provider,
)
from wled_controller.storage.weather_source import WeatherSource
from wled_controller.storage.weather_source_store import WeatherSourceStore
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class _WeatherRuntime:
    """Polls a weather provider on a timer and caches the latest result."""

    def __init__(self, source: WeatherSource, provider: WeatherProvider) -> None:
        self._source_id = source.id
        self._provider = provider
        self._latitude = source.latitude
        self._longitude = source.longitude
        self._interval = max(60, source.update_interval)

        self._data: WeatherData = DEFAULT_WEATHER
        self._lock = threading.Lock()
        self._running = False
        self._thread: Optional[threading.Thread] = None

    @property
    def data(self) -> WeatherData:
        with self._lock:
            return self._data

    def start(self) -> None:
        if self._running:
            return
        self._running = True
        self._thread = threading.Thread(
            target=self._poll_loop, daemon=True,
            name=f"Weather-{self._source_id[:12]}",
        )
        self._thread.start()
        logger.info(f"Weather runtime started: {self._source_id}")

    def stop(self) -> None:
        self._running = False
        if self._thread is not None:
            self._thread.join(timeout=10.0)
            self._thread = None
        logger.info(f"Weather runtime stopped: {self._source_id}")

    def update_config(self, source: WeatherSource) -> None:
        self._latitude = source.latitude
        self._longitude = source.longitude
        self._interval = max(60, source.update_interval)

    def fetch_now(self) -> WeatherData:
        """Force an immediate fetch (used by test endpoint)."""
        result = self._provider.fetch(self._latitude, self._longitude)
        with self._lock:
            self._data = result
        return result

    def _poll_loop(self) -> None:
        # Fetch immediately on start
        self._do_fetch()
        last_fetch = time.monotonic()

        while self._running:
            time.sleep(1.0)
            if time.monotonic() - last_fetch >= self._interval:
                self._do_fetch()
                last_fetch = time.monotonic()

    def _do_fetch(self) -> None:
        result = self._provider.fetch(self._latitude, self._longitude)
        with self._lock:
            self._data = result
        logger.debug(
            f"Weather {self._source_id}: code={result.code} "
            f"temp={result.temperature:.1f}C wind={result.wind_speed:.0f}km/h"
        )


class WeatherManager:
    """Ref-counted pool of weather runtimes."""

    def __init__(self, store: WeatherSourceStore) -> None:
        self._store = store
        # source_id -> (runtime, ref_count)
        self._runtimes: Dict[str, tuple] = {}
        self._lock = threading.Lock()

    def acquire(self, source_id: str) -> _WeatherRuntime:
        """Get or create a runtime for the given weather source. Increments ref count."""
        with self._lock:
            if source_id in self._runtimes:
                runtime, count = self._runtimes[source_id]
                self._runtimes[source_id] = (runtime, count + 1)
                return runtime

            source = self._store.get(source_id)
            provider = create_provider(source.provider, source.provider_config)
            runtime = _WeatherRuntime(source, provider)
            runtime.start()
            self._runtimes[source_id] = (runtime, 1)
            return runtime

    def release(self, source_id: str) -> None:
        """Decrement ref count; stop runtime when it reaches zero."""
        with self._lock:
            if source_id not in self._runtimes:
                return
            runtime, count = self._runtimes[source_id]
            if count <= 1:
                runtime.stop()
                del self._runtimes[source_id]
            else:
                self._runtimes[source_id] = (runtime, count - 1)

    def get_data(self, source_id: str) -> WeatherData:
        """Get cached weather data for a source (creates runtime if needed)."""
        with self._lock:
            if source_id in self._runtimes:
                runtime, _count = self._runtimes[source_id]
                return runtime.data
        # No active runtime — do a one-off fetch via ensure_runtime
        runtime = self._ensure_runtime(source_id)
        return runtime.data

    def fetch_now(self, source_id: str) -> WeatherData:
        """Force an immediate fetch for the test endpoint."""
        runtime = self._ensure_runtime(source_id)
        return runtime.fetch_now()

    def update_source(self, source_id: str) -> None:
        """Hot-update runtime config when the source is edited."""
        with self._lock:
            if source_id not in self._runtimes:
                return
            runtime, count = self._runtimes[source_id]
        try:
            source = self._store.get(source_id)
            runtime.update_config(source)
        except Exception as e:
            logger.warning(f"Failed to update weather runtime {source_id}: {e}")

    def _ensure_runtime(self, source_id: str) -> _WeatherRuntime:
        """Get or create a runtime (for API control of idle sources)."""
        with self._lock:
            if source_id in self._runtimes:
                runtime, count = self._runtimes[source_id]
                return runtime

        source = self._store.get(source_id)
        provider = create_provider(source.provider, source.provider_config)
        runtime = _WeatherRuntime(source, provider)
        runtime.start()
        with self._lock:
            if source_id not in self._runtimes:
                self._runtimes[source_id] = (runtime, 0)
            else:
                runtime.stop()
                runtime, _count = self._runtimes[source_id]
        return runtime

    def shutdown(self) -> None:
        """Stop all runtimes."""
        with self._lock:
            for source_id, (runtime, _count) in list(self._runtimes.items()):
                runtime.stop()
            self._runtimes.clear()
        logger.info("Weather manager shut down")
