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

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

from core.constants import HTTP_HEADERS
from providers.spot.spot_provider import SpotProvider


class HTTPSpotProvider(SpotProvider):
    """Generic spot provider class for providers that request data via HTTP(S). Just for convenience to avoid code
    duplication. Subclasses of this query the individual APIs for data."""

    def __init__(self, name, provider_config, url, poll_interval):
        super().__init__(name, provider_config)
        self._url = url
        self._poll_interval = poll_interval
        self._thread = None
        self._stop_event = Event()
        self._wakeup_event = Event()

    def start(self):
        # Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
        # subsequent polls, so start() returns immediately and the application can continue starting.
        logging.info("Set up query of " + self.name + " spot API every " + str(self._poll_interval) + " seconds.")
        self._thread = Thread(target=self._run, name=f"HTTPSpotProvider-{self.name}")
        self._thread.start()

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

    def force_poll(self):
        """Trigger an immediate poll without waiting for the normal interval."""

        self._wakeup_event.set()

    def _run(self):
        while True:
            self._wakeup_event.clear()
            self._poll()
            self._wakeup_event.wait(timeout=self._poll_interval)
            if self._stop_event.is_set():
                break

    def _poll(self):
        try:
            # Request data from API
            logging.debug("Polling " + self.name + " spot API...")
            http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30))
            # Check response code was good
            if http_response.ok:
                # Pass off to the subclass for processing
                new_spots = self._http_response_to_spots(http_response)
                # Submit the new spots for processing. There might not be any spots for the less popular programs.
                if new_spots:
                    self._submit_batch(new_spots)

                self.status = "OK"
                self.last_update_time = datetime.now(pytz.UTC)
                logging.debug("Received data from " + self.name + " spot API.")
            else:
                self.status = "Error"
                logging.warning(f"HTTP {http_response.status_code} when calling {self.name} spot API.")

        except ConnectionError:
            logging.warning(f"Connection error when accessing {self.name} spots API.")
        except (ConnectTimeout, ReadTimeout):
            logging.warning(f"Timeout when accessing {self.name} spots API.")
        except Exception:
            self.status = "Error"
            logging.exception("Exception in HTTP Spot Provider (" + self.name + ")")
            self._stop_event.wait(timeout=1)

    def _http_response_to_spots(self, http_response):
        """Convert an HTTP response returned by the API into spot data. The whole response is provided here so the subclass
        implementations can check for HTTP status codes if necessary, and handle the response as JSON, XML, text, whatever
        the API actually provides."""

        raise NotImplementedError("Subclasses must implement this method")
