#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 smarttab

import os

os.environ["GIT_PYTHON_REFRESH"] = "quiet"

import boto3
import os.path
from passpy import Store
from datetime import datetime, timedelta
import click
from colorama import init as colorama_init
from colorama import Fore, Style

colorama_init()

ENVS = ['test', 'qa', 'prod']

SEC = {'key': [4, 6], 'secret': [4, 4]}

store = Store()


class Account:
    """Describes particular aws account - that mean - access_key + secret,
    and list of keys, that are present at the account, for credentials"""
    indent = 0

    def __init__(self, **kwargs):
        self.env = kwargs['env']
        self.local = self._read_str_to_dict(kwargs['local'])
        self.iam = None
        self.keys = None

    @staticmethod
    def _transcode_key(content):
        now = datetime.now(content['CreateDate'].tzinfo)
        return {
            'user_name': content['UserName'],
            'access_key_id': content['AccessKeyId'],
            'status': content['Status'],
            'create_date': content['CreateDate'],
            'age': now - content['CreateDate']
        }

    @staticmethod
    def _read_str_to_dict(input):
        res = {}
        for l in input.split('\n'):
            k, v = l.partition("=")[::2]
            k, v = k.strip(), v.strip()
            if k != '' and v != '':
                res[k.strip()] = v.strip()
        return res

    @staticmethod
    def _write_key_to_str(key):
        """prepare data to be stored in pass-store"""
        return f"aws_access_key_id = {key['access_key_id']}\naws_secret_access_key = {key['secret_access_key']}"

    @staticmethod
    def anonymize_key(key):
        """exchange part of key string with '*' to anonymize"""
        s = SEC['key'][0]
        e = SEC['key'][1]
        return key[:s] + '*' * len(key[s:-e]) + key[-e:]

    @staticmethod
    def anonymize_password(password):
        """exchange part of password string with '*' to anonymize"""
        s = SEC['secret'][0]
        e = SEC['secret'][1]
        return password[:s] + '*' * len(password[s:-e]) + password[-e:]

    def _instantiate_iam(self):
        if self.iam is None:
            self.iam = boto3.client(
                'iam',
                aws_access_key_id=self.local['aws_access_key_id'],
                aws_secret_access_key=self.local['aws_secret_access_key'])

    def fetch_keys(self):
        print(" " * self.indent + ". fetching keys")
        self._instantiate_iam()
        response = self.iam.list_access_keys()
        keys = response
        keys_data = []
        for content in keys['AccessKeyMetadata']:
            keys_data.append(self._transcode_key(content))
        self.keys = keys_data
        print(" " * self.indent + "> keys fetched")

    def create_new_key(self, user_name):
        """Create new key"""
        print(" " * self.indent + ". creating new key")
        self._instantiate_iam()
        response = self.iam.create_access_key(UserName=user_name)
        if response['ResponseMetadata']['HTTPStatusCode'] == 200:
            print(" " * self.indent + "> key created")
        else:
            print(" " * self.indent +
                  f"create new access key - response " +
                  f"httpCode:{response['ResponseMetadata']['HTTPStatusCode']} responseId:{response['ResponseMetadata']['RequestId']} " +
                  f"user:{response['AccessKey']['UserName']} " +
                  f"accessKey:{self.anonymize_key(response['AccessKey']['AccessKeyId'])} " +
                  f"status:{response['AccessKey']['Status']} " +
                  f"secret:{self.anonymize_password(response['AccessKey']['SecretAccessKey'])}")
        key = self._transcode_key(response['AccessKey'])
        key['secret_access_key'] = response['AccessKey']['SecretAccessKey']
        content = self._write_key_to_str(key)
        store.set_key(f"aws/{self.env}", content, force=True)
        print(" " * self.indent + "> key stored")

    def remove_key(self, key):
        """remove key"""
        print(" " * self.indent + ". removing oldest")
        self._instantiate_iam()
        response = self.iam.delete_access_key(
            UserName=key['user_name'],
            AccessKeyId=key['access_key_id'])
        self.keys.remove(key)
        if response['ResponseMetadata']['HTTPStatusCode'] == 200:
            print(" " * self.indent + "> done")
        else:
            print(" " * self.indent + Fore.RED + response + Style.RESET_ALL)

    def roll_key(self):
        """Find oldest key, and call roll on it"""
        print(" " * self.indent + ". rolling keys")
        self._instantiate_iam()
        if self.keys is None:
            self.fetch_keys()

        if len(self.keys) > 1:
            # look what to delete, at least one key is needed, to
            # get into env...
            two_years = timedelta(days=365 * 2)
            keys = sorted(self.keys, reverse=True,
                          key=lambda i: i['age'].total_seconds() if i['status'] == 'Active'
                          else i['age'].total_seconds() + two_years.total_seconds())
            self.remove_key(keys[0])
        if len(self.keys) == 0:
            raise Exception('No keys at server? Something went wrong...')
        else:
            user_name = self.keys[0]['user_name']
        self.create_new_key(user_name)

    def __repr__(self):
        return "{}({})".format(self.__class__, self.__dict__)


def get_all():
    """get all the data"""
    res = {}
    for e in ENVS:
        access_context = store.get_key(f'aws/{e}')
        res[e] = Account(env=e, local=access_context)
    return res


def put_credentials(env):
    """Write new credentials to ~/.aws/credentials"""
    print(". putting credentials into ~/.aws/credentials")
    print(f"env:{env.env} - accessKey:{Account.anonymize_key(env.local['aws_access_key_id'])} - "
          f"secret:{Account.anonymize_password(env.local['aws_secret_access_key'])}")
    content = f"""[default]
aws_access_key_id = {env.local['aws_access_key_id']}
aws_secret_access_key = {env.local['aws_secret_access_key']}
"""
    with open(os.path.expanduser('~/.aws/credentials'), "w") as f:
        f.truncate(0)
        f.write(content)


def put_account(account_name):
    """Write new account name to ~/.aws/account"""
    content = f"""{account_name}"""
    with open(os.path.expanduser('~/.aws/account'), "w") as f:
        f.truncate(0)
        f.write(content)


@click.group()
def cli():
    pass


@cli.command(help="Show all remote information")
@click.argument('side', default='local')
def list_envs(side):
    envs = get_all()
    if 'local' != side.lower():
        for name in envs:
            env = envs[name]
            env.fetch_keys()
    for name in envs:
        e = envs[name]
        print(f"{e.env}\n local:\n  "
              f"{Account.anonymize_key(e.local['aws_access_key_id'])} - "
              f"{Account.anonymize_password(e.local['aws_secret_access_key'])}")
        if 'local' != side.lower():
            print(" remote: ")
            for key in e.keys:
                print(f"  user:{key['user_name']} - "
                      f"accessKey:{Account.anonymize_key(key['access_key_id'])} - "
                      f"status:{key['status']} - age:{key['age']}")


@cli.command(help="Roll keys for all environments")
def roll():
    try:
        envs = get_all()
        for name in envs.keys():
            print(f"rolling {name}")
            env = envs[name]
            try:
                env.indent = 2
                env.roll_key()
            finally:
                env.indent = 0
    except Exception as e:
        print('Error, while rolling keys. ', str(e))


@click.command(help='Set environment in ~/.aws - credentials, account name')
@click.argument("name", nargs=1)
def use(name):
    try:
        envs = get_all()
        if not name in envs.keys():
            print(Fore.RED + f"sorrry {name} is not valid env name - plase see list-envs" + Style.RESET_ALL)
        else:
            env = envs[name]
            put_credentials(env)
            put_account(name)
    except Exception as e:
        print('Error, while taking key. ', str(e))


@click.command(help='Set credentials for environment. ')
@click.argument("name", nargs=1)
def set_credentials(name):
    try:
        print(f"""You are going to create pass credentials for {name} environment.
please go, visit AWS console, and create new secret. Then provide both key, and secret, when prompted""")
        key = {}
        print("a")
        key['access_key_id'] = input("provide accessKey: ")
        key['secret_access_key'] = input("provide secret: ")
        print("> storing into pass storage")
        content = Account._write_key_to_str(key)
        store.set_key(f"aws/{name}", content, force=True)
        print("> key stored")
    except Exception as e:
        print('Error, while storing key. ', str(e))


cli.add_command(list_envs)
cli.add_command(roll)
cli.add_command(use)
cli.add_command(set_credentials)

if __name__ == '__main__':
    if not store.is_init():
        print(Fore.RED + "Pass store is not initialized.")
        print("You need gpg key, and pass store initialized.")
        print("Please visit https://texxas.io/2018/12/aws-pass-tool.html - for step by step procedure." + Style.RESET_ALL)
        exit(1)
    cli()
