#!/usr/bin/env python
# Copyright 2014 - Rackspace Hosting
#
#    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.

"""
Setup everything for a Github-hosted repo to use solum as its CI tool.
"""

import argparse
import base64
import getpass
import json
import os
import random
import re
import string
import tempfile

import httplib2
import six
import yaml

from solumclient import client as solum_client
from solumclient.common import cliutils


SOLUM_API_VERSION = '1'
CREDENTIALS = {}
PLAN_TEMPLATE = {"version": 1,
                 "name": "chef",
                 "description": "chef testy",
                 "artifacts": []}


def _get_solum_client():
    args = {}
    args['os_username'] = os.getenv('OS_USERNAME', '')
    args['os_password'] = os.getenv('OS_PASSWORD', '')
    args['os_tenant_name'] = os.getenv('OS_TENANT_NAME', '')
    args['os_auth_url'] = os.getenv('OS_AUTH_URL', '')
    args['solum_url'] = os.getenv('SOLUM_URL', '')

    try:
        client = solum_client.get_client(SOLUM_API_VERSION, **args)
        return client
    except Exception as ex:
        print("Error in getting Solum client: %s" % ex)
        exit(1)


def _get_token(git_url):
    # Get an OAuth token with the scope of 'repo' for the user
    if git_url in CREDENTIALS and 'token' in CREDENTIALS[git_url]:
        return CREDENTIALS[git_url]['token']

    repo_pat = re.compile(r'github\.com[:/](.+?)/(.+?)($|/$|\.git$|\.git/$)')
    match = repo_pat.search(git_url)
    if match:
        user_org_name = match.group(1)
        repo = match.group(2)
    else:
        print('Failed parsing %s' % git_url)
        exit(1)

    full_repo_name = '/'.join([user_org_name, repo])
    username = six.moves.input("Username for repo '%s' [%s]: " %
                               (full_repo_name, user_org_name))
    if not username:
        username = user_org_name
    password = getpass.getpass("Password: ")
    # TODO(james_li): add support for two-factor auth
    CREDENTIALS[git_url] = {}
    CREDENTIALS[git_url]['user'] = username
    CREDENTIALS[git_url]['password'] = password
    CREDENTIALS[git_url]['full_repo'] = full_repo_name

    http = httplib2.Http()
    auth = base64.encodestring(username + ':' + password)
    headers = {'Authorization': 'Basic ' + auth,
               'Content-Type': 'application/json'}
    # 'note' field has to be unique
    note = 'Solum-status-' + ''.join(random.sample(string.lowercase, 5))
    data = {'scopes': 'repo', 'note': note}

    # TODO(james_li): make the url configurable
    resp, content = http.request('https://api.github.com/authorizations',
                                 'POST', headers=headers,
                                 body=json.dumps(data))

    if resp['status'] == '201' or resp['status'] == '200':
        content_dict = json.loads(content)
        CREDENTIALS[git_url]['token'] = str(content_dict['token'])
        return CREDENTIALS[git_url]['token']
    else:
        print('Failed to get token from Github')
        exit(1)


def _filter_trigger_url(url):
    filtered_url = url
    url_pattern = re.compile(r'^(http://)(.+)')
    match = url_pattern.search(url)
    if match:
        filtered_url = ''.join(['https://', match.group(2)])
    else:
        print('Cannot filter trigger url, to use the original one')
    return filtered_url


def get_planfile(git_uri, app_name, cmd, public):
    plan_dict = dict.copy(PLAN_TEMPLATE)
    plan_dict['name'] = app_name
    plan_dict['description'] = git_uri  # Put repo uri as plan desc.
    arti = {"name": "chef", "artifact_type": "chef",
            "content": {}, "language_pack": "auto"}
    arti['content']['href'] = git_uri
    if not public:
        arti['content']['private'] = True
    arti['unittest_cmd'] = cmd
    plan_dict['artifacts'].append(arti)

    # Create a Github token and insert it into plan file
    for arti in plan_dict['artifacts']:
        arti['status_token'] = _get_token(arti['content']['href'])
    plan_file = tempfile.NamedTemporaryFile(suffix='.yaml',
                                            prefix='solum_',
                                            delete=False)
    plan_file.write(yaml.dump(plan_dict, default_flow_style=False))
    plan_file_name = plan_file.name
    plan_file.close()
    return plan_file_name


def create_plan(client, plan_file):
    cmd = ['solum', 'app', 'create', plan_file]
    print(' '.join(cmd))
    with open(plan_file) as definition_file:
        definition = definition_file.read()

    plan = client.plans.create(definition)
    fields = ['uuid', 'name', 'description', 'uri']
    data = dict([(f, getattr(plan, f, ''))
                 for f in fields])
    cliutils.print_dict(data, wrap=72)

    if data['uri'] is None:
        print('Error: no uri found in plan creation')
        exit(1)

    # get public keys in the case of private repos
    artifacts = getattr(plan, 'artifacts', [])
    for arti in artifacts:
        content = getattr(arti, 'content', {})
        if 'public_key' in content and 'href' in content:
            CREDENTIALS[content['href']]['pub_key'] = content['public_key']

    return data['uri']


def create_assembly(client, app_name, plan_uri):
    cmd = ['solum', 'assembly', 'create', app_name, plan_uri]
    print(' '.join(cmd))
    assembly = client.assemblies.create(name=app_name, plan_uri=plan_uri)

    fields = ['uuid', 'name', 'description', 'status', 'application_uri',
              'trigger_uri']
    data = dict([(f, getattr(assembly, f, ''))
                 for f in fields])
    cliutils.print_dict(data, wrap=72)

    trigger_uri = data['trigger_uri']
    if trigger_uri is None:
        print('Error in trigger uri')
        exit(1)

    return trigger_uri


def create_webhook(trigger_uri):
    # Create github web hooks for pull requests
    for key in CREDENTIALS.keys():
        user = CREDENTIALS[key]['user']
        password = CREDENTIALS[key]['password']
        auth = base64.encodestring(user + ':' + password)
        http = httplib2.Http()
        # TODO(james_li): make this url configurable
        github_url = ('https://api.github.com/repos/%s/hooks' %
                      CREDENTIALS[key]['full_repo'])
        headers = {'Authorization': 'Basic ' + auth,
                   'Content-Type': 'application/json'}
        data = {'name': 'web',
                'events': ['pull_request', 'commit_comment'],
                'config': {'content_type': 'json',
                           'url': trigger_uri}}

        resp, _ = http.request(github_url, 'POST',
                               headers=headers,
                               body=json.dumps(data))

        if resp['status'] != '201' and resp['status'] != '200':
            print("Failed to create web hooks")
            print("Make sure you have access to repo '%s'" % key)
            exit(1)


def add_ssh_keys(args):
    if args.public:
        return

    # add public keys
    for key in CREDENTIALS.keys():
        if ('pub_key' in CREDENTIALS[key] and
                CREDENTIALS[key]['pub_key'] is not None):
            user = CREDENTIALS[key]['user']
            password = CREDENTIALS[key]['password']
            auth = base64.encodestring(user + ':' + password)
            http = httplib2.Http()
            if args.user_key:
                # TODO(james_li): make the url configurable
                github_url = 'https://api.github.com/user/keys'
            else:
                github_url = ('https://api.github.com/repos/%s/keys' %
                              CREDENTIALS[key]['full_repo'])
            headers = {'Authorization': 'Basic ' + auth,
                       'Content-Type': 'application/json'}
            data = {'title': 'devops@Solum',
                    'key': CREDENTIALS[key]['pub_key']}

            resp, _ = http.request(github_url, 'POST',
                                   headers=headers,
                                   body=json.dumps(data))

            if resp['status'] != '201' and resp['status'] != '200':
                if args.user_key:
                    print("Failed to add a ssh key to the account %s" % user)
                else:
                    print("Failed to add a deploy key to the repo %s" % key)
                exit(1)


def validate_args(args):
    if len(args.command) == 0 or len(args.git_uri) == 0:
        print("Please input for --test-cmd and --git-uri")
        exit(1)

    # try to use a correct git uri
    pat = re.compile(r'github\.com[:/](.+?)/(.+?)($|/.*$|\.git$|\.git/.*$)')
    match = pat.search(args.git_uri)
    if match:
        user_org_name = match.group(1)
        repo = match.group(2)
        if args.public:
            correct_uri = 'https://github.com/%s/%s' % (user_org_name, repo)
        else:
            correct_uri = 'git@github.com:%s/%s.git' % (user_org_name, repo)
        return correct_uri
    else:
        print("The input git uri seems not right")
        if args.public:
            print("The correct format is: https://github.com/<USER>/<REPO>")
        else:
            print("The correct format is: git@github.com:<USER>/<REPO>.git")
        exit(1)


def main(args):
    git_uri = validate_args(args)
    client = _get_solum_client()
    plan_file = get_planfile(git_uri, args.app_name, args.command, args.public)
    print('\n')
    print("************************* Starting setup *************************")
    print('\n')
    plan_uri = create_plan(client, plan_file)
    add_ssh_keys(args)
    try:
        os.remove(plan_file)
    except OSError:
        print('Cannot remove %s. Skip and move forward...' % plan_file)

    trigger_uri = create_assembly(client, args.app_name, plan_uri)
    create_webhook(_filter_trigger_url(trigger_uri))
    print('Successfully created Solum plan, assembly and webhooks!')


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('app_name', help="app name")
    parser.add_argument('--git-uri', required=True, dest='git_uri',
                        help="git repo uri")
    parser.add_argument('--test-cmd', required=True, dest='command',
                        help="entrypoint to run tests")
    parser.add_argument('--public', action='store_true', default=False,
                        dest='public', help="public repo, defaults to False")
    parser.add_argument('--user-key', action='store_true', default=False,
                        dest='user_key', help="add SSH key to the user account"
                                              " rather than the repo,"
                                              " defaults to False")

    args = parser.parse_args()
    main(args)
