#!/usr/bin/python -u
# coding=utf-8
"""
Generate certificates via Let's Encrypt
"""
import re
from subprocess import check_output, check_call
from os import path

import click
from colorama import Fore
import pexpect

# Extract the file/challenge from the LetsEncrypt output e.g.
CREX = re.compile(
    ".well-known\/acme-challenge\/(\S+) before continuing:\s+(\S+)",
    re.MULTILINE
)

MODULE_CONFIG = 'module.yaml'  # The file in our project root
APPENGINE_URL = ("https://console.cloud.google.com/" +
                 "appengine/settings/certificates")


def get_default_email():
    """Get a default user email from the git config."""
    return check_output(['git', 'config', 'user.email']).strip()


@click.command()
@click.option('--appid', '-A', prompt=True)
@click.option('--test/--no-test', default=True)
@click.option('--domains', '-d', multiple=True)
@click.option('--app-path', default=path.abspath(path.dirname(__file__)))
@click.option('--acme-path', required=True)
@click.option('--email', default=get_default_email)
def gen(test, appid, domains, acme_path, app_path, email):
    """Regenerate the keys.

    Run all the steps, being:
        1. Call Let's Encrypt
        2. Capture the challenges from the LE output
        3. Deploy the AppEngine module
        4. Print Cert. to terminal
    """
    common_name = domains[0]  # noqa

    sans = " ".join(domains)  # noqa
    click.echo("""
        APPID: {appid}
        Test: {test}
        Common Name: {common_name}
        Domain(s): {sans}
        App Path: {app_path}
        ACME path: {acme_path}
        User Email: {email}
    """.format(**{
        k: Fore.YELLOW + str(v) + Fore.RESET
        for k, v in locals().items()
    }))

    CERT_PATH = acme_path
    KEY_PATH = acme_path
    CHAIN_PATH = acme_path
    FULLCHAIN_PATH = acme_path
    CONFIG_DIR = acme_path
    WORK_DIR = path.join(acme_path, 'tmp')
    LOG_DIR = path.join(acme_path, 'logs')

    cmd = [
        'letsencrypt',
        'certonly',
        '--rsa-key-size',
        '2048',
        '--manual',
        '--agree-tos',
        '--manual-public-ip-logging-ok',
        '--text',
        '--cert-path', CERT_PATH,
        '--key-path', KEY_PATH,
        '--chain-path', CHAIN_PATH,
        '--fullchain-path', FULLCHAIN_PATH,
        '--config-dir', CONFIG_DIR,
        '--work-dir', WORK_DIR,
        '--logs-dir', LOG_DIR,
        '--email', email,
        '--domain', ",".join(domains),
    ]
    if test:
        cmd.append('--staging')

    print("$ " + Fore.MAGENTA + " ".join(cmd) + Fore.RESET)

    le = pexpect.spawn(" ".join(cmd))

    out = ''
    idx = le.expect(["Press ENTER", "Select the appropriate number"])
    if idx == 1:
        # 1: Keep the existing certificate for now
        # 2: Renew & replace the cert (limit ~5 per 7 days)
        print le.before + le.after
        le.interact("\r")
        print "..."
        le.sendline("")
        if le.expect(["Press ENTER", pexpect.EOF]) == 1:
            # EOF - User chose to not update certs.
            return
    out += le.before
    # Hit "Enter" for each domain; we extract all challenges at the end;
    # We stop just at the last "Enter to continue" so we can publish
    # our challenges on AppEngine.
    for i in xrange(len(domains) - 1):
        le.sendline("")
        le.expect("Press ENTER")
        out += le.before

    # The challenges will be in `out` in the form of CREX
    challenges = CREX.findall(out)
    if not challenges:
        raise Exception("Expected challenges from the output")

    for filename, challenge in challenges:
        filepath = path.join(app_path, "challenges", filename)
        print "[%s]\n\t%s\n\t=> %s" % (
            Fore.BLUE + filepath + Fore.RESET,
            Fore.GREEN + filename + Fore.RESET,
            Fore.YELLOW + challenge + Fore.RESET
        )
        with open(filepath, 'w') as f:
            f.write(challenge)

    # Deploy to AppEngine
    cmd = [
        'appcfg.py',
        'update',
        '-A', appid,
        path.join(app_path, MODULE_CONFIG)
    ]
    print("$ " + Fore.MAGENTA + " ".join(cmd) + Fore.RESET)
    check_call(cmd)

    # After deployment, continue the Let's Encrypt (which has been waiting
    # on the last domain)
    le.sendline("")
    le.expect(pexpect.EOF)
    le.close()
    if le.exitstatus:
        print Fore.RED + "\nletsencrypt failure: " + Fore.RESET + le.before
        return
    print "\nletsencrypt complete.", le.before

    # Convert the key to a format AppEngine can use
    # LE seems to choose the domain at random, so we have to pluck it.

    CPATH_REX = (
        "Your certificate and chain have been saved at (.+)fullchain\.pem\."
    )
    outstr = le.before.replace("\n", "").replace('\r', '')
    results = re.search(CPATH_REX, outstr, re.MULTILINE)

    LIVE_PATH = "".join(results.group(1).split())
    CHAIN_PATH = path.join(LIVE_PATH, "fullchain.pem")
    PRIVKEY_PATH = path.join(LIVE_PATH, "privkey.pem")
    cmd = [
        'openssl', 'rsa',
        '-in', PRIVKEY_PATH,
        '-outform', 'pem',
        '-inform', 'pem'
    ]
    print "$ " + Fore.MAGENTA + " ".join(cmd) + Fore.RESET
    priv_text = check_output(cmd)
    with open(CHAIN_PATH, 'r') as cp:
        pub_text = cp.read()

    print """
    --- Private Key ---
    at {PRIVKEY_PATH}
    (the above file must be converted with {cmd} to a format usable by
     AppEngine, the result of which will be as follows)

{priv_text}

    --- Public Key Chain ---
    at {CHAIN_PATH}

{pub_text}

    ✄ Copy the above into the respective fields of AppEngine at

      https://console.cloud.google.com/appengine/settings/certificates

    """.format(
        PRIVKEY_PATH=PRIVKEY_PATH,
        priv_text=Fore.RED + priv_text + Fore.RESET,
        CHAIN_PATH=CHAIN_PATH,
        pub_text=Fore.BLUE + pub_text + Fore.RESET,
        cmd=Fore.MAGENTA + " ".join(cmd) + Fore.RESET,
    )


if __name__ == '__main__':
    gen()