##
# Copyright 2016 Canonical Ltd.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
##

from charmhelpers.core import unitdata
from charmhelpers.core.hookenv import (
    action_fail,
    action_get,
    action_set,
    config,
    log,
    status_set,
    DEBUG,
)

from charms.reactive.flags import register_trigger

from charms.reactive import (
    clear_flag,
    set_flag,
    when,
    when_not,
    when_any,
)
import charms.sshproxy
import os
import subprocess

# Register a trigger so that we can respond to config.changed, even if
# it's being cleared by another handler
register_trigger(when='config.changed',
                 set_flag='sshproxy.reconfigure')


@when_any('config.changed', 'sshproxy.reconfigure')
def ssh_configured():
    """Check if charm is properly configured.

    Check to see if the charm is configured with SSH credentials. If so,
    set a state flag that can be used to execute ssh-only actions.

    For example:

    @when('sshproxy.configured')
    def run_remote_command(cmd):
        ...

    @when_not('sshproxy.configured')
    def run_local_command(cmd):
        ...
    """
    log("Checking sshproxy configuration", DEBUG)
    cfg = config()
    ssh_keys = ['ssh-hostname', 'ssh-username',
                'ssh-password', 'ssh-private-key']

    if all(k in cfg for k in ssh_keys):

        # Store config in unitdata so it's accessible to sshproxy
        db = unitdata.kv()
        db.set('config', cfg)

        # Explicitly flush the kv so it's immediately available
        db.flush()

        log("Verifying ssh credentials...", DEBUG)
        (verified, output) = charms.sshproxy.verify_ssh_credentials()
        if verified:
            log("SSH credentials verified.", DEBUG)
            set_flag('sshproxy.configured')
            status_set('active', 'Ready!')
        else:
            clear_flag('sshproxy.configured')
            status_set('blocked', "Verification failed: {}".format(output))
    else:
        log("No ssh credentials configured", DEBUG)
        clear_flag('sshproxy.configured')
        status_set('blocked', 'Invalid SSH credentials.')


def generate_ssh_key():
    """Generate a new 4096-bit rsa keypair.

    If there is an existing keypair for this unit, it will be overwritten.
    """
    cfg = config()
    if all(k in cfg for k in ['ssh-key-type', 'ssh-key-bits']):
        keytype = cfg['ssh-key-type']
        bits = str(cfg['ssh-key-bits'])
        privatekey = '/root/.ssh/id_juju_sshproxy'
        publickey = "{}.pub".format(privatekey)

        if os.path.exists(privatekey):
            os.remove(privatekey)
        if os.path.exists(publickey):
            os.remove(publickey)

        cmd = "ssh-keygen -t {} -b {} -N '' -f {}".format(
            keytype,
            bits,
            privatekey
        )

        output, err = charms.sshproxy.run_local([cmd])
        if len(err) == 0:
            return True
    return False


@when('actions.generate-ssh-key')
def action_generate_ssh_key():
    """Generate a new 4096-bit rsa keypair.

    If there is an existing keypair for this unit, it will be overwritten.
    """
    try:
        if not generate_ssh_key():
            action_fail('Unable to generate ssh key.')
    except subprocess.CalledProcessError as e:
        action_fail('Command failed: %s (%s)' %
                    (' '.join(e.cmd), str(e.output)))
    finally:
        clear_flag('actions.generate-ssh-key')


def get_ssh_public_key():
    """Get the public SSH key of this unit."""
    publickey_path = '/root/.ssh/id_juju_sshproxy.pub'
    publickey = None
    if os.path.exists(publickey_path):
        with open(publickey_path, 'r') as f:
            publickey = f.read()

    return publickey


@when('actions.get-ssh-public-key')
def action_get_ssh_public_key():
    """Get the public SSH key of this unit."""
    try:
        action_set({'pubkey': get_ssh_public_key()})
    except subprocess.CalledProcessError as e:
        action_fail('Command failed: %s (%s)' %
                    (' '.join(e.cmd), str(e.output)))
    finally:
        clear_flag('actions.get-ssh-public-key')


@when('actions.verify-ssh-credentials')
def action_verify_ssh_credentials():
    """Verify the ssh credentials have been installed to the VNF.

    Attempts to run a stock command - `hostname` on the remote host.
    """
    try:
        (verified, output) = charms.sshproxy.verify_ssh_credentials()
        action_set({
            'output': output,
            'verified': verified,
        })
        if not verified:
            action_fail("Verification failed: {}".format(
                output,
            ))
    finally:
        clear_flag('actions.verify-ssh-credentials')


@when('actions.run')
def run_command():
    """Run an arbitrary command.

    Run an arbitrary command, either locally or over SSH with the configured
    credentials.
    """
    try:
        cmd = action_get('command')
        output, err = charms.sshproxy._run(cmd)
        if len(err):
            action_fail("Command '{}' returned error code {}".format(cmd, err))
        else:
            action_set({'output': output})
    except subprocess.CalledProcessError as e:
        action_fail('Command failed: %s (%s)' %
                    (' '.join(e.cmd), str(e.output)))
    finally:
        clear_flag('actions.run')


@when_not('sshproxy.installed')
def install_vnf_ubuntu_proxy():
    """Install and Configure SSH Proxy."""

    log("Generating SSH key...", DEBUG)
    generate_ssh_key()
    set_flag('sshproxy.installed')