import json
import socket
import requests

from wurker.models import ApiAuth


def make_drone_clients(drone_status:list, auth:tuple=None):
    """Create DroneClient(s) for the given wurker_status drone data. Will raise exceptions on failed connections."""

    # TODO Allow token auth in future. For now, just Basic HTTP Auth
    username, password = auth or (None, None)

    clients = {}
    for status in drone_status:
        app_id = str(status.app_id)
        clients[app_id] = DroneClient(
            app_id = app_id,
            name = status.name,
            slug = status.slug,
            private_ip = status.private_ip,
            public_ip = status.public_ip,
            server_domain = status.server_domain,
            username = username,
            password = password,
        )

    return clients


class DroneClient:
    """An API client for Queens to query their Drones."""

    hostname = None
    protocol = "https"

    def __init__(
            self,
            app_id="",
            name="",
            slug="",
            private_ip="",
            public_ip="",
            server_domain="",
            username="",
            password="",
    ):

        self.app_id = app_id
        self.name = name
        self.slug = slug
        self.private_ip = private_ip
        self.public_ip = public_ip
        self.server_domain = server_domain

        self.auth = (username, password) if username and password else None

        self._init_hostname()

    def _init_hostname(self):
        """Pick the first working domain or IP and use it as the Drone host"""

        hostnames = [h for h in [self.server_domain, self.public_ip, self.private_ip,] if h]
        for hostname in hostnames:
            if hostname and self._hostname_resolves(hostname):
                self.hostname = hostname
                return

        # Still here? That's an exception. Talk to DevOps
        raise Exception(f"Could not set hostname from any of: {hostnames}")

    def _hostname_resolves(self, hostname:str):
        """Do a socket-level resolver check."""

        try:
            socket.gethostbyname(hostname)
            return True
        except socket.error:
            return False

    def url(self, uri:str):
        """In case the protocol is changed after object created."""

        uri = uri.strip("/")
        self.protocol = "http" if self.hostname == self.private_ip else "https"
        return f"{self.protocol}://{self.hostname}/{uri}"

    def request(self, method:str, uri:str, data=None):
        """Call the Drone API URI using the given method."""

        url = self.url(uri)

        if method == "get":
            response = requests.get(url, auth=self.auth)
        elif method == "post":
            headers = {"Content-Type": "application/json"}
            response = requests.post(url, data=json.dumps(data), headers=headers, auth=self.auth)
        else:
            raise Exception(f"Unsupported HTTP method: '{method}'")

        response.raise_for_status()
        return response.json()

    def get_status(self):
        """Get the result of the 'status' subcommand from this Drone's API."""

        status = self.request("get", "api/status")

        if "db_conf" in status and status["db_conf"]:
            if "password" in status["db_conf"]:
                status["db_conf"]["password"] = "****"

        return status

    def get_show(self, option:str='', key:str=''):
        """Get the show-any data from the Drone as JSON."""

        uri = f"api/show"
        if option:
            uri += f"/{option}"
            if key:
                uri += f"/{key}"

        return self.request("get", uri)

    def post_control(self, action:str):
        """Send a control action on the Drone."""

        return self.request("post", f"api/control/{action}")

    def post_add(self, option:str, data=None):
        """Send an add request to the Drone."""

        return self.request("post", f"api/add/{option}", data=data)

    def post_edit(self, option:str, key:list, data=None):
        """Send an edit request to the Drone."""

        key0 = key[0]
        key1 = None if len(key) < 2 else key[1]

        uri = f"api/edit/{option}/{key0}"
        if key1 is not None:
            uri += f"/{key1}"

        return self.request("post", uri, data=data)

    def post_remove(self, option:str, key:list):
        """Send a remove request to the Drone."""

        key0 = key[0]
        key1 = None if len(key) < 2 else key[1]

        uri = f"api/remove/{option}/{key0}"
        if key1 is not None:
            uri += f"/{key1}"

        return self.request("post", uri)

    def post_kill(self, option:str=None, key:str=None):
        """Send a kill request to the Drone."""

        uri = f"api/kill"
        if option is not None:
            uri += f"/{option}"
            if key is not None:
                uri += f"/{key}"

        return self.request("post", uri)

    def get_report(self, slug:str, start:str=None, end:str=None):
        """TODO Send a report request to the Drone."""

        uri = f"api/report/{slug}"
        if start is not None and end is not None:
            uri += f"/daterange/{start}/{end}"

        return self.request("get", uri)  # TODO What about query params?


    def __repr__(self):
        return f"<{self.__class__.__name__}>({self.name=}, {self.slug=}, {self.app_id=}, " \
            + f"{self.hostname=}, {self.private_ip=}, " \
            + f"{self.public_ip=}, {self.server_domain=})"
