import json
import click
import socket
import subprocess

from sqlalchemy import func, select

from wurker.settings import conf
from wurker.cli.base import BaseCommand
from wurker.drone import make_drone_clients
from wurker.db import get_drone_status, make_engine, schema_exists
from wurker.models import Base, WurkerBee, WurkerControl, WurkerStatus


class StatusCommand(BaseCommand):
    """Encapsulate status command."""

    def __init__(self, logger=None):
        BaseCommand.__init__(self, logger=logger)

        # We might need drone API auth
        self.drone_api_auth = conf.ApiAuth


    def run(self, drones:list=[], return_result:bool=False):
        """Display Wurker status."""

        # Run run() on remote Drone(s) API?
        if drones:
            return self.run_drone(drones, return_result=return_result)

        def _get_private_ip(target_ip='8.8.8.8'):
            """Get the local IP for the NIC used to access the target_ip."""

            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.connect((target_ip, 1))
            local_ip = sock.getsockname()[0]
            sock.close()
            return local_ip

        def _get_public_ip():
            """Perform a `curl ipinfo.io/ip` to get the public IP of this agent host."""

            cmd = ["curl", "ipinfo.io/ip"]
            try:
                proc = subprocess.run(
                    cmd,
                    stdout = subprocess.PIPE,
                    stderr = subprocess.DEVNULL,
                    check = True
                )
                public_ip = proc.stdout.decode("utf-8").strip()
            except:
                return None
            else:
                return public_ip

        def _print_status(status, return_result=False):
            if return_result:
                return status

            click.echo(json.dumps(status, sort_keys=True, indent=2))

        # Assemble status dict
        status = {
            "app_id": None,
            "name": None,
            "slug": None,
            "is_queen": False,
            "env": None,
            "private_ip": None,
            "public_ip": None,
            "ui_domain": None,
            "config_dirs": None,
            "local_db": None,
            "local_db_conn": False,
            "remote_db": None,
            "remote_db_conn": False,
            "remote_db_schema": False,
            "control": None,
            "bees": None,
            "drones": None,
            "status": False,
        }

        status["app_id"] = conf.app_id
        if not status["app_id"]:
            click.echo("Missing valid 'app_id' setting in wurker.toml", err=True)

        status["name"] = conf.name
        status["slug"] = conf.slug
        status["is_queen"] = conf.is_queen
        status["env"] = conf.env or "production"

        status["private_ip"] = _get_private_ip()
        status["public_ip"] = _get_public_ip()
        status["domain"] = conf.domain

        status["config_dirs"] = {
            "etc_root": conf.etc_root,
            "opt_root": conf.opt_root,
            "data_root": conf.data_root,
        }

        status["remote_db"] = vars(self.remote_db_conf)
        status["local_db"] = vars(self.local_db_conf)

        # Make sure we can create a DB connection
        db_confs = {"remote_db_conn": self.remote_db_conf, "local_db_conn": self.local_db_conf}
        for db_attr, db_conf in db_confs.items():
            try:
                engine = make_engine(db_conf=db_conf)
                conn = engine.connect()
                status[db_attr] = True
            except:
                return _print_status(status, return_result=return_result)

        # Make sure the schema exists
        try:
            schema_exists(self.remote_db_conf, Base)
            status["remote_db_schema"] = True
        except:
            return _print_status(status, return_result=return_result)

        # Make sure we have a control record
        try:
            engine = make_engine(db_conf=self.remote_db_conf)
            conn = engine.connect()
        except:
            return _print_status(status, return_result=return_result)

        stmt = select(WurkerControl).where(WurkerControl.app_id == status["app_id"])
        control = conn.execute(stmt).first()
        if control is None:
            return _print_status(status, return_result=return_result)

        status["control"] = {
            "reload": control.reload,
            "pause": control.pause,
            "seed": control.seed,
        }

        # Queens also get Drone control records
        if status["is_queen"]:
            stmt = select(
                WurkerControl,
                WurkerStatus
            ).join(
                WurkerStatus, WurkerControl.app_id == WurkerStatus.app_id
            ).where(
                WurkerControl.app_id != status["app_id"]
            )

            drones = conn.execute(stmt).all()
            if len(drones):
                status["drones"] = {}
                for drone in drones:
                    status["drones"][str(drone.app_id)] = {
                        "slug": drone.slug,
                        "name": drone.name,
                        "is_queen": drone.is_queen,
                        "env": drone.env,
                        "private_ip": drone.private_ip,
                        "public_ip": drone.public_ip,
                        "server_domain": drone.server_domain,
                        "control": {
                            "app_id": str(drone.app_id),
                            "reload": drone.reload,
                            "pause": drone.pause,
                            "seed": drone.seed,
                        },
                    }

        # Get counts for display
        bees_ct = {"enabled": 0, "disabled": 0,}

        stmt = select(func.count(WurkerBee.id).label("bee_ct")).where(
            WurkerBee.enabled == True,
            WurkerBee.app_id == status["app_id"]
        )
        bees_ct["enabled"] = conn.execute(stmt).first().bee_ct

        stmt = select(func.count(WurkerBee.id).label("bee_ct")).where(
            WurkerBee.enabled == False,
            WurkerBee.app_id == status["app_id"]
        )
        bees_ct["disabled"] = conn.execute(stmt).first().bee_ct

        status["bees"] = bees_ct

        # Now display the data sorta nice
        status["status"] = True
        return _print_status(status, return_result=return_result)


    def run_drone(self, drones:list, return_result:bool=False):
        """Run via Drone API(s)."""

        drone_status = get_drone_status(self.remote_db_conf, self.app_id, drones=drones)
        if drone_status is None or not len(drone_status):
            click.echo(f"WARNING: Queen ({self.app_id}) Drones have no 'wurker_status' data", err=True)
            return

        drone_clients = make_drone_clients(drone_status, auth=self.drone_api_auth)

        results = {}
        for app_id, drone in drone_clients.items():
            result = drone.get_status()

            if not result:
                click.echo(f"WARNING: No 'status' result from Drone '{drone.slug}' ({drone.app_id})", err=True)
                continue

            results[app_id] = result

        if not results:
            click.echo(f"No 'status' results from Drones", err=True)
            return

        if return_result:
            return results

        # OK, everything else would've printed to stderr... this is the final successful output
        click.echo(json.dumps(results, sort_keys=True, indent=2))
