#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import hashlib
import hmac
import json
import os
import sys
import time
import urllib.parse as url
from base64 import b64encode
from base64 import decodebytes
from typing import Dict

import boto3

from iobot.utils import format_cloudformation_overrides_for_sam
from iobot.utils import init_config
# import datetime


def message_signature(slack_request_timestamp: int, slack_signing_secret: str, body: str, version: str = 'v0') -> str:
    signature = bytes(f'{version}:{slack_request_timestamp}:{body}', 'utf-8')
    mac = hmac.new(
        bytes(slack_signing_secret, 'utf-8'),
        signature,
        digestmod=hashlib.sha256
    ).hexdigest()
    mac_versioned = f'{version}={mac}'
    return mac_versioned


def slack_request_timestamp() -> int:
    return int(time.time())


def get_secret(parameter: str, path='secrets/dev.json') -> str:
    with open(path) as file:
        file_data = json.loads(file.read())
        for secret in file_data:
            if secret.get('ParameterKey') == parameter:
                return secret.get('ParameterValue')


def slack_bearer_token() -> str:
    return get_secret('SlackBearerToken')


def slack_signing_secret() -> str:
    return get_secret('SlackSigningSecret')


def slack_user_token() -> str:
    return get_secret('SlackUserToken')


def slack_verification_token() -> str:
    return get_secret('SlackVerificationToken')


def pagerduty_token() -> str:
    return get_secret('PagerDutyToken')


def servicenow_credentials() -> (str, str):
    return get_secret('ServiceNowUsername'), get_secret('ServiceNowPassword')


def slater_credentials() -> (str, str):
    return get_secret('SLaterUsername'), get_secret('SLaterPassword')


def tableau_token() -> str:
    return get_secret('TableauToken')


def cloudability_token() -> str:
    return get_secret('CloudabilityToken')


def grafanacloud_token() -> str:
    return get_secret('GrafanaCloudToken')


def sendgrid_token() -> str:
    return get_secret('SendgridToken')


def get_unittest_config(env: Dict = {}):
    return init_config(config_file='environments/unit.ini', env=env)


def get_stack_outputs(stack_name: str) -> set:
    session = boto3.session.Session()
    client = session.client('cloudformation')
    stack = client.describe_stacks(StackName=stack_name).get('Stacks')[0]
    return stack.get('Outputs')


OUTPUT_KEYS: Dict = {
    'DestinationQueueUrl': 'DESTINATION_QUEUE_URL',
    'PagerdutyTokenArn': 'PAGERDUTY_TOKEN_ARN',
    'SlackBearerTokenArn': 'SLACK_BEARER_TOKEN_ARN',
    'SlackSigningSecretArn': 'SLACK_SIGNING_SECRET_ARN',
    'TableauTokenArn': 'TABLEAU_TOKEN_ARN',
    'CloudabilityTokenArn': 'CLOUDABILITY_TOKEN_ARN',
    'VerificationTokenArn': 'VERIFICATION_TOKEN_ARN',
    'SendgridTokenArn': 'SENDGRID_TOKEN_ARN',
    'SlackAuthArn': 'SLACK_AUTH_ARN'
}


def get_secrets_arns(stack_name: str) -> Dict:
    secrets_arns: Dict = {}
    for output in get_stack_outputs(stack_name):
        if output.get('OutputKey') in OUTPUT_KEYS:
            key = OUTPUT_KEYS[output.get('OutputKey')]
            secrets_arns[key] = output.get('OutputValue')

    return secrets_arns


def export_secret_arns(stack_name: str) -> None:
    secrets = get_secrets_arns(stack_name)
    for key in secrets.keys():
        os.environ[key] = secrets[key]
        os.environ.putenv(key, secrets[key])

    os.environ['LOCAL_TESTING'] = 'true'

    return


def get_secrets(outputs: dict = None) -> Dict:
    if not outputs:
        outputs = {
            'SlackSigningSecret': 'SLACK_SIGNING_SECRET',
            'SlackBearerToken': 'SLACK_BOT_TOKEN',
            'SendgridToken': 'SENDGRID_TOKEN'
        }

    secrets: Dict = {}
    for secret in outputs.keys():
        key = outputs[secret]
        secrets[key] = get_secret(secret)

    return secrets


def export_secrets(outputs: dict = None) -> None:
    if not outputs:
        outputs = {
            'SlackSigningSecret': 'SLACK_SIGNING_SECRET',
            'SlackBearerToken': 'SLACK_BOT_TOKEN',
            'SendgridToken': 'SENDGRID_TOKEN'
        }

    secrets = get_secrets(outputs=outputs)
    for key in secrets.keys():
        os.environ[key] = secrets[key]
        os.environ.putenv(key, secrets[key])

    os.environ['LOCAL_TESTING'] = 'true'

    return


def unexport_secret_arns() -> None:
    for key in OUTPUT_KEYS.values():
        del os.environ[key]

    return


def unexport_secrets(outputs: dict = None) -> None:
    if not outputs:
        outputs = {
            'SlackSigningSecret': 'SLACK_SIGNING_SECRET',
            'SlackBearerToken': 'SLACK_BOT_TOKEN',
            'SendgridToken': 'SENDGRID_TOKEN'
        }

    for key in outputs.values():
        del os.environ[key]

    return


def get_secrets_as_shell_eval(stack_name: str) -> str:
    secrets = get_secrets_arns(stack_name)
    exports: list = []
    for key in secrets.keys():
        exports.append(f'export {key}="{secrets[key]}"')

    secrets = get_secrets()
    for key in secrets.keys():
        exports.append(f'export {key}="{secrets[key]}"')

    exports.append('export LOCAL_TESTING="true"')

    newline = '\n'
    return newline.join(exports)


def event_wrapper(args: Dict = {}):
    export_secret_arns(os.environ['STACK_NAME'])
    event = json.loads(sys.stdin.read())
    timestamp: int = slack_request_timestamp()
    verification_token: str = slack_verification_token()
    signing_secret: str = slack_signing_secret()

    body = {}

    if 'Records' in event:
        # treat it as sqs
        body = decodebytes(bytes(event["Records"][0]["body"], 'utf-8'))
        slack_event = json.loads(body)
        slack_event['token'] = verification_token

        if args.message_text:
            slack_event['event']['text'] = args.message_text
            event["Records"][0]["body"] = b64encode(
                json.dumps(slack_event).encode()).decode('utf-8')

    else:
        # treat it as apigw
        if 'challenge' in event['body']:
            pass
        else:
            body = url.parse_qs(event['body'])
            body['token'] = verification_token
            if args.message_text:
                body['text'] = args.message_text
            else:
                # look back at the original event
                if 'text' in event['body']:
                    body['text'] = ''

            if args.slash_command:
                body['command'] = args.slash_command

            event['body'] = url.urlencode(body, doseq=True)

        signature: str = message_signature(
            timestamp, signing_secret, event['body'])
        event['headers']['X-Slack-Request-Timestamp'] = f'{timestamp}'
        event['headers']['X-Slack-Signature'] = f'{signature}'
        event['multiValueHeaders']['X-Slack-Request-Timestamp'][0] = f'{timestamp}'
        event['multiValueHeaders']['X-Slack-Signature'][0] = f'{signature}'

    return event


def parse(argv):
    parser = argparse.ArgumentParser(
        add_help=True,
        prog="iobot.testing",
        description="iobot testing framework",
        epilog="""More info on this project:

        https://gitlab.com/pearson-io-bots/iobot

        Raise questions @ john.torres@pearson.com
        """)

    subparsers = parser.add_subparsers()
    parser_environment = subparsers.add_parser(
        'environment', help='environment help')
    parser_environment.add_argument('-e', '--export', dest='export', default=False,
                                    required=False, action='store_true', help="export environment variables")
    parser_environment.add_argument('-p', '--print-exports', dest='print_exports', default=False,
                                    required=False, action='store_true', help='print environment variables')
    parser_environment.set_defaults(command='environment')
    parser_events = subparsers.add_parser('events', help='events help')
    parser_events.add_argument('-p', '--patch-event', dest='patch_event', default=False,
                               required=False, action='store_true', help='patch inbound slack event')
    parser_events.add_argument('-s', '--slash-command', dest='slash_command', metavar="/slater",
                               type=str, required=False, help="slash command as you would type it in slack")
    parser_events.add_argument('-t', '--message-text', dest='message_text', default="",
                               type=str, required=False, help="message text as you would type it in slack")
    parser_events.set_defaults(command='events')
    parser_cloudformation = subparsers.add_parser('sam', help='sam help')
    parser_cloudformation.add_argument('-f', '--format-parameters-overrides', dest='format_parameters_overrides',
                                       nargs='+', type=str, required=True, help="convert cloudformation parameter overrides file to sam parameters")
    parser_cloudformation.set_defaults(command='sam')
    args, unknown = parser.parse_known_args(argv)

    if len(unknown) > 0:
        err = f"unrecognized arguments: {unknown}"
        return args, err, parser.format_help()
    else:
        return args, None, parser.format_help()


if __name__ == '__main__':
    if not os.environ.get('AWS_DEFAULT_REGION') and not os.environ.get('AWS_REGION'):
        print('export one of "AWS_DEFAULT_REGION" or "AWS_REGION" to proceed')
        sys.exit(255)

    if not os.environ.get('STACK_NAME'):
        print('you must export "STACK_NAME" to proceed')
        sys.exit(254)

    stack_name: str = os.environ['STACK_NAME']
    args, err, helpmsg = parse(sys.argv[1:])

    if args.command == "environment":
        if args.print_exports:
            print(get_secrets_as_shell_eval(stack_name=stack_name))
    elif args.command == "events":
        if args.patch_event:
            print(json.dumps(event_wrapper(args)))
        else:
            print(sys.stdin.read(), end='')
    elif args.command == "sam":
        if args.format_parameters_overrides:
            print(format_cloudformation_overrides_for_sam(
                args.format_parameters_overrides))
