import logging
from datetime import datetime, timezone, timedelta
from threading import Thread, Event

import pytz
import requests
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout

from core.constants import HTTP_HEADERS
from providers.solarconditions.ionosonde_utils import compute_band_states
from providers.solarconditions.solar_conditions_provider import SolarConditionsProvider

POLL_INTERVAL = 900  # 15 minutes
KC2G_URL = "https://prop.kc2g.com/api/stations.json"
HISTORY_HOURS = 24


class KC2GProp(SolarConditionsProvider):
    """Solar conditions provider using ionosonde data from prop.kc2g.com. The API returns only the latest reading per
    station, so this provider polls every 15 minutes and accumulates a 24-hour time series by merging each new reading
    into the persisted ionosonde_data, producing the same data structure as GIROIonosonde.

    Designed to run alongside GIROIonosonde even though they produce similar data. KC2G is more reliable and is always
    online, but has fewer stations and does not provide LUF data."""

    def __init__(self, provider_config):
        super().__init__("KC2G Propagation Data", provider_config)
        self._thread = None
        self._stop_event = Event()

    def start(self):
        logging.info(f"Set up query of KC2G ionosonde data API every {POLL_INTERVAL} seconds.")
        self._thread = Thread(target=self._run, name="KC2GPropProvider")
        self._thread.start()

    def stop(self):
        self._stop_event.set()

    def _run(self):
        while True:
            self._poll()
            if self._stop_event.wait(timeout=POLL_INTERVAL):
                break

    def _poll(self):
        try:
            logging.debug("Polling KC2G ionosonde data...")
            http_response = requests.get(KC2G_URL, headers=HTTP_HEADERS, timeout=(5, 30))
            if not http_response.ok:
                logging.warning(f"HTTP {http_response.status_code} when calling KG2G ionosonde API.")
                return

            now = datetime.now(timezone.utc)
            cutoff_ts = (now - timedelta(hours=HISTORY_HOURS)).timestamp()

            # Start from existing ionosonde_data so the accumulated time series survives across polls and restarts and
            # stations provided only by GIROIonosonde are not discarded
            ionosonde_data = dict(self._solar_conditions.ionosonde_data or {})
            updated_count = 0

            for reading in http_response.json():
                station = reading.get("station", {})
                ursi = station.get("code")
                name = station.get("name")
                if not ursi or not name:
                    continue

                time_str = reading.get("time")
                if not time_str:
                    continue
                try:
                    ts = datetime.fromisoformat(time_str)
                    if ts.tzinfo is None:
                        ts = ts.replace(tzinfo=timezone.utc)
                    ts_float = ts.timestamp()
                except ValueError:
                    continue

                # Skip readings outside our history window (some stations have months-old data)
                if ts_float < cutoff_ts:
                    continue

                fof2_val = reading.get("fof2")
                muf_val = reading.get("mufd")
                if fof2_val is None and muf_val is None:
                    continue

                # Merge this reading into the existing time series for the station.
                existing = ionosonde_data.get(ursi, {})
                fof2_dict = dict(existing.get("fof2") or {})
                muf_dict = dict(existing.get("muf") or {})
                # LUF is not available from KC2G; carry forward whatever GIRO has written.
                luf_dict = {float(t): v for t, v in (existing.get("luf") or {}).items()}

                fof2_dict[ts_float] = fof2_val
                muf_dict[ts_float] = muf_val

                # Trim all series to the 24-hour window.
                fof2_dict = {t: v for t, v in fof2_dict.items() if t >= cutoff_ts}
                muf_dict = {t: v for t, v in muf_dict.items() if t >= cutoff_ts}
                luf_dict = {t: v for t, v in luf_dict.items() if t >= cutoff_ts}

                band_states = compute_band_states(fof2_dict, muf_dict, luf_dict)
                ionosonde_data[ursi] = {
                    "ursi": ursi,
                    "name": name,
                    "fof2": fof2_dict or None,
                    "muf": muf_dict or None,
                    "luf": luf_dict or None,
                    "band_states": band_states,
                }
                updated_count += 1

            self.update_data({"ionosonde_data": ionosonde_data})
            self.status = "OK"
            self.last_update_time = datetime.now(pytz.UTC)
            logging.debug(f"Updated KC2G ionosonde data for {updated_count} stations.")

        except ConnectionError:
            logging.warning("Connection error when accessing KC2G ionosonde API.")
        except (ConnectTimeout, ReadTimeout):
            logging.warning(f"Timeout when accessing KC2G ionosonde API.")
        except Exception:
            self.status = "Error"
            logging.exception("Exception in KC2G ionosonde data provider")
            self._stop_event.wait(timeout=1)
