import functools
import getpass
import grp
import logging
import os
import random
import re
import shutil
import subprocess
import sys
import tempfile

import click

from seamless.signer import Signer


logger = logging.getLogger(__name__)
LOG_FILE_PATH = '/var/log/seamless/seamless.log'
USER_GROUP = 'seamless'

key_file_format_re = re.compile(r'^(?P<algo>ssh-\w+)\s+(?P<key>\S+)\s+(?P<user>\S+)\s*')


def system_setup():
    try:
        grp.getgrnam(USER_GROUP)
    except KeyError:
        print('group "{}" does not exist. creating.'.format(USER_GROUP))
        print(subprocess.check_output('addgroup {}'.format(USER_GROUP), shell=True))
    if not os.path.exists(LOG_FILE_PATH):
        print('log file {} does not exist. creating'.format(LOG_FILE_PATH))
        subprocess.check_output('''
            mkdir -p {log_dir};
            touch {log_file};
            chown -R root:{group} {log_dir};
            chmod 0750 {log_dir};
            chmod 0620 {log_file};
        '''.format(
            log_dir=os.path.dirname(LOG_FILE_PATH),
            log_file=LOG_FILE_PATH,
            group=USER_GROUP,
        ), shell=True)


class Scope(object):
    def __init__(self, name):
        self.name = name

    @property
    def secret_file(self):
        return os.path.join(self.path, 'secret')

    @property
    def authorized_keys_file(self):
        return os.path.join(self.path, '.ssh/authorized_keys')

    @property
    def key_dir(self):
        return os.path.join(self.path, 'keys')

    @property
    def path(self):
        return os.path.expanduser('~{}'.format(self.name))

    def init(self, secret=None):
        system_setup()
        print("creating user {}, home={}".format(self.name, self.path))
        subprocess.check_output('useradd {name} --create-home --skel {skel} --comment "seamless user" --groups {group}'.format(
            name=self.name,
            skel=tempfile.mkdtemp(),
            group=USER_GROUP,
        ), shell=True)
        if not secret:
            secret = '%x' % random.getrandbits(256)
        with open(self.secret_file, 'w') as f:
            f.write(secret.strip())
        subprocess.check_output("""
            mkdir {ssh_dir};
            mkdir {key_dir};
            touch {key_file};
            chown -R {user}:{user} {home_dir};
            chmod 400 {secret_file};
            chmod 700 {ssh_dir}
        """.format(
            user=self.name,
            secret_file=self.secret_file,
            key_file=self.authorized_keys_file,
            home_dir=self.path,
            ssh_dir=os.path.join(self.path, '.ssh'),
            key_dir=self.key_dir,
        ), shell=True)

    def delete(self):
        group = grp.getgrnam(USER_GROUP)
        if self.name not in group.gr_mem:
            print("{} is not a member of group {}, aborting.".format(self.name, USER_GROUP))
            return
        subprocess.check_output('deluser {name} --remove-home'.format(self.name))

    def get_secret(self):
        with open(self.secret_file, 'r') as f:
            return f.read()

    def get_token(self, username):
        return Signer(self.get_secret()).sign(username)

    def add(self, keyfile, username=None):
        if not username:
            username = os.path.basename(keyfile)
        shutil.copyfile(keyfile, os.path.join(self.key_dir, username))

    def remove(self, username):
        raise NotImplementedError()

    def sync(self):
        lines = []
        for filename in os.listdir(self.key_dir):
            if filename.startswith('.'):
                continue
            path = os.path.join(self.key_dir, filename)
            command = '{python} -m seamless.cli token {name} --user {user}'.format(python=sys.executable, name=self.name, user=filename)
            with open(path, 'r') as f:
                key = f.read()
                match = key_file_format_re.match(key)
                if not match:
                    print('{} does not look like a valid ssh key, skipping.'.format(path))
                    continue
                key = '{algo} {key} {user}'.format(**match.groupdict())
                print("found key for {}".format(filename))
                lines.append('command="{command}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty {key}\n'.format(
                    command=command,
                    key=key.strip(),
                    user=filename,
                ))

        with open(self.authorized_keys_file, 'w') as f:
            f.write('# autogenerated by seamless. do not edit.\n')
            for line in lines:
                f.write(line)
        subprocess.check_output('chmod 400 {key_file}; chown {user}:{user} {key_file}'.format(user=self.name, key_file=self.authorized_keys_file), shell=True)


def require_root(cmd):
    @functools.wraps(cmd)
    def wrapped_command(*args, **kwargs):
        if getpass.getuser() != 'root':
            print("must be root")
            sys.exit(1)
        return cmd(*args, **kwargs)
    return wrapped_command


def print_version(ctx, param, value):
    from seamless import __version__
    print(__version__)
    ctx.exit()


@click.group()
@click.option('--version', is_flag=True, is_eager=True, expose_value=False, callback=print_version)
def main():
    if os.path.exists(LOG_FILE_PATH):
        handler = logging.FileHandler(LOG_FILE_PATH)
        handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
        logger.addHandler(handler)
        logger.setLevel(logging.INFO)
    logger.info(' '.join(sys.argv))


@main.command()
@click.argument('name')
@click.option('--secret')
@require_root
def init(name, secret):
    scope = Scope(name)
    scope.init(secret=secret)


@main.command()
@click.argument('name')
@click.option('--user')
def token(name, user):
    try:
        scope = Scope(name)
        print scope.get_token(user)
    except:
        # don't leak exceptions to clients
        logger.exception('token creation failed')


@main.command()
@click.argument('name')
@require_root
def sync(name):
    scope = Scope(name)
    scope.sync()


@main.command()
@click.argument('name')
@click.argument('keyfile')
@click.option('--user')
@require_root
def add(name, keyfile, user):
    scope = Scope(name)
    scope.add(keyfile, user)
    scope.sync()


@main.command()
@click.argument('name')
@click.argument('user')
@require_root
def remove(name, user):
    scope = Scope(name)
    scope.remove(user)
    scope.sync()


@main.command()
@click.argument('name')
@require_root
def delete(name):
    scope = Scope(name)
    scope.delete()


if __name__ == '__main__':
    main()
