import os
from datetime import datetime
from threading import Thread, Event

import psutil
import pytz

from core.cleanup import CLEANUP_TIMER
from core.config import SERVER_OWNER_CALLSIGN
from core.constants import SOFTWARE_VERSION
from core.data_providers import DATA_PROVIDERS
from core.data_store import DATA_STORE
from core.prometheus_metrics_handler import memory_use_gauge, spots_gauge, alerts_gauge
from server.webserver import WEB_SERVER


class StatusReporter:
    """Provides a timed update of the application's status data."""

    def __init__(self, run_interval):
        """Constructor"""

        self._run_interval = run_interval
        self._thread = None
        self._stop_event = Event()
        self._startup_time = datetime.now(pytz.UTC)

        DATA_STORE.status_data["software_version"] = SOFTWARE_VERSION
        DATA_STORE.status_data["server_owner_callsign"] = SERVER_OWNER_CALLSIGN

    def start(self):
        """Start the reporter thread"""

        self._thread = Thread(target=self._run, name="StatusReporter")
        self._thread.start()

    def stop(self):
        """Stop any threads and prepare for application shutdown"""

        self._stop_event.set()

    def _run(self):
        """Thread entry point: report immediately on startup, then on each interval until stopped"""

        while True:
            self._report()
            if self._stop_event.wait(timeout=self._run_interval):
                break

    def _report(self):
        """Write status information"""

        DATA_STORE.status_data["uptime"] = (datetime.now(pytz.UTC) - self._startup_time).total_seconds()
        DATA_STORE.status_data["mem_use_mb"] = round(psutil.Process(os.getpid()).memory_info().rss / (1024 * 1024), 3)
        DATA_STORE.status_data["num_spots"] = len(DATA_STORE.spots.values())
        DATA_STORE.status_data["num_alerts"] = len(DATA_STORE.alerts.values())
        DATA_STORE.status_data["spot_providers"] = list(
            map(lambda p: {"name": p.name, "enabled": p.enabled,
                           "enabled_by_default_in_web_ui": p.enabled_by_default_in_web_ui, "status": p.status,
                           "last_updated": p.last_update_time.replace(
                               tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0,
                           "last_spot": p.last_spot_time.replace(
                               tzinfo=pytz.UTC).timestamp() if p.last_spot_time.year > 2000 else 0},
                DATA_PROVIDERS.spot_providers))
        DATA_STORE.status_data["alert_providers"] = list(
            map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
                           "last_updated": p.last_update_time.replace(
                               tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0},
                DATA_PROVIDERS.alert_providers))
        DATA_STORE.status_data["solar_condition_providers"] = list(
            map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
                           "last_updated": p.last_update_time.replace(
                               tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0},
                DATA_PROVIDERS.solar_condition_providers))
        DATA_STORE.status_data["static_data_providers"] = list(
            map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
                           "last_updated": p.last_update_time.replace(
                               tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0},
                DATA_PROVIDERS.static_data_providers))
        DATA_STORE.status_data["sig_ref_data_providers"] = list(
            map(lambda p: {"sig_name": p.sig_name, "enabled": p.enabled, "status": p.status,
                           "last_updated": p.last_update_time.replace(
                               tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0,
                           "reference_count": p.reference_count},
                DATA_PROVIDERS.sig_ref_data_providers))
        DATA_STORE.status_data["callsign_data_providers"] = list(
            map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
                           "last_updated": p.last_update_time.replace(
                               tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0,
                           "lookup_count": p.lookup_count},
                DATA_PROVIDERS.callsign_data_providers))
        DATA_STORE.status_data["cleanup"] = {"status": CLEANUP_TIMER.status,
                                        "last_ran": CLEANUP_TIMER.last_cleanup_time.replace(
                                            tzinfo=pytz.UTC).timestamp() if CLEANUP_TIMER.last_cleanup_time else 0}
        DATA_STORE.status_data["webserver"] = {"status": WEB_SERVER.web_server_metrics["status"],
                                               "last_api_access": WEB_SERVER.web_server_metrics[
                                                   "last_api_access_time"].replace(
                                                   tzinfo=pytz.UTC).timestamp() if WEB_SERVER.web_server_metrics[
                                                   "last_api_access_time"] else 0,
                                               "api_access_count": WEB_SERVER.web_server_metrics["api_access_counter"],
                                               "last_page_access": WEB_SERVER.web_server_metrics[
                                                   "last_page_access_time"].replace(
                                                   tzinfo=pytz.UTC).timestamp() if WEB_SERVER.web_server_metrics[
                                                   "last_page_access_time"] else 0,
                                               "page_access_count": WEB_SERVER.web_server_metrics[
                                                   "page_access_counter"]}

        # Update Prometheus metrics
        memory_use_gauge.set(psutil.Process(os.getpid()).memory_info().rss)
        spots_gauge.set(len(DATA_STORE.spots.values()))
        alerts_gauge.set(len(DATA_STORE.alerts.values()))
