import json
import os
import tempfile

from fabric.api import env, local, put, run, sudo
from fabric.operations import _prefix_commands, _prefix_env_vars

__all__ = [
    "sshagent_run",
    "call_python",
    "ec2_hostnames",
    "ec2_instances",
    "answer_sudo",
]


def sshagent_run(cmd, user=None):
    """
    Helper function.
    Runs a command with SSH agent forwarding enabled.

    Note:: Fabric (and paramiko) can't forward your SSH agent.
    This helper uses your system's ssh to do so.
    """
    # Handle context manager modifications
    wrapped_cmd = _prefix_commands(_prefix_env_vars(cmd), "remote")
    if user is None:
        user = env.user
    opts = ["-A", "-o ServerAliveInterval=60"]
    if env.disable_known_hosts:
        opts += ["-o StrictHostKeyChecking=no", "-o UserKnownHostsFile=/dev/null"]
    try:
        host, port = env.host_string.split(":")
        opts.append("-p %s" % port)
    except ValueError:
        host = env.host_string
    opts = " ".join(opts)
    return local("ssh %s %s@%s '%s'" % (opts, user, host, wrapped_cmd))


def call_python(method, *args):
    """
    Call the given ``method'' with the given ``args'' in Python on the
    remote server.  ``method'' should be the full Python path to the
    method.  Only JSON-serializable arguments and return values are
    supported.
    """
    module = ".".join(method.split(".")[:-1])
    args = json.dumps(args)[1:-1]
    output = run(
        "/usr/bin/env python3 -c 'import json, {module};"
        "print(json.dumps({method}({args})))'"
        "".format(module=module, method=method, args=args)
    )
    return json.loads(output)


def ec2_hostnames(*args, **kwargs):
    """
    Returns a list of hostnames for the specified filters.
    """
    from fabulaws.ec2 import EC2Service

    return EC2Service().public_dns(*args, **kwargs)


def ec2_instances(*args, **kwargs):
    """
    Returns a list of instances for the specified filters.
    """
    from fabulaws.ec2 import EC2Service

    return EC2Service().instances(*args, **kwargs)


def answer_sudo(cmd, *args, **kwargs):
    """
    Answers questions presented via standard out according to the
    given answers.  The ``answers`` keyword argument should be a list of
    (question, answer) pairs.  The questions are in regular expression format,
    so ensure that any special characters are appropriately escaped.
    """
    answers = kwargs.pop("answers", [])
    if answers:
        # use shared memory rather than potentially writing passwords to the
        # most likely unencrypted disk
        tmpdir = os.path.exists("/dev/shm") and "/dev/shm" or None
        script = tempfile.NamedTemporaryFile(dir=tmpdir)
        script.writelines(
            [
                "import pexpect, sys\n",
                "child = pexpect.spawn('{0}')\n".format(cmd),
                # the following line may be enabled for debugging purposes, but
                # will spill passphrases in plain text in log files
                # "child.logfile = sys.stdout\n"
            ]
        )
        for question, answer in answers:
            script.writelines(
                [
                    "child.expect('{0}')\n".format(question),
                    "child.sendline('{0}')\n".format(answer),
                ]
            )
        # this is important, otherwise pexect will kill the process before it's
        # finished
        script.writelines(["child.wait()\n"])
        script.flush()
        remote_file = "/dev/shm/{0}".format(os.path.basename(script.name))
        put(script.name, remote_file, mirror_local_mode=True)
        cmd = "python {0}".format(remote_file)
    result = sudo(cmd, *args, **kwargs)
    if answers:
        run("rm {0}".format(remote_file))
    return result
