#!/usr/bin/env python

"""Convert SAM templates to CloudFormation templates.

Known limitations: cannot transform CodeUri pointing at local directory.

Usage:
  sam-translate.py --template-file=sam-template.yaml [--verbose] [--output-template=<o>]
  sam-translate.py package --template-file=sam-template.yaml --s3-bucket=my-bucket [--verbose] [--output-template=<o>]
  sam-translate.py deploy --template-file=sam-template.yaml --s3-bucket=my-bucket --capabilities=CAPABILITY_NAMED_IAM --stack-name=my-stack [--verbose] [--output-template=<o>]

Options:
  --template-file=<i>       Location of SAM template to transform [default: template.yaml].
  --output-template=<o>     Location to store resulting CloudFormation template [default: transformed-template.json].
  --s3-bucket=<s>           S3 bucket to use for SAM artifacts when using the `package` command
  --capabilities=<c>        Capabilities
  --stack-name=<n>          Unique name for your CloudFormation Stack
  --verbose                 Enables verbose logging

"""
import json
import logging
import os
import platform
import subprocess
import sys

import boto3

from docopt import docopt  # type: ignore[import]
from functools import reduce

my_path = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, my_path + "/..")

from samtranslator.public.translator import ManagedPolicyLoader
from samtranslator.translator.transform import transform
from samtranslator.yaml_helper import yaml_parse
from samtranslator.model.exceptions import InvalidDocumentException

LOG = logging.getLogger(__name__)
cli_options = docopt(__doc__)
iam_client = boto3.client("iam")
cwd = os.getcwd()

if cli_options.get("--verbose"):
    logging.basicConfig(level=logging.DEBUG)
else:
    logging.basicConfig()


def execute_command(command, args):  # type: ignore[no-untyped-def]
    try:
        aws_cmd = "aws" if platform.system().lower() != "windows" else "aws.cmd"
        command_with_args = [aws_cmd, "cloudformation", command] + list(args)

        LOG.debug("Executing command: %s", command_with_args)

        subprocess.check_call(command_with_args)

        LOG.debug("Command successful")
    except subprocess.CalledProcessError as e:
        # Underlying aws command will print the exception to the user
        LOG.debug("Exception: %s", e)
        sys.exit(e.returncode)


def get_input_output_file_paths():  # type: ignore[no-untyped-def]
    input_file_option = cli_options.get("--template-file")
    output_file_option = cli_options.get("--output-template")
    input_file_path = os.path.join(cwd, input_file_option)
    output_file_path = os.path.join(cwd, output_file_option)

    return input_file_path, output_file_path


def package(input_file_path, output_file_path):  # type: ignore[no-untyped-def]
    template_file = input_file_path
    package_output_template_file = input_file_path + "._sam_packaged_.yaml"
    s3_bucket = cli_options.get("--s3-bucket")
    args = [
        "--template-file",
        template_file,
        "--output-template-file",
        package_output_template_file,
        "--s3-bucket",
        s3_bucket,
    ]

    execute_command("package", args)  # type: ignore[no-untyped-call]

    return package_output_template_file


def transform_template(input_file_path, output_file_path):  # type: ignore[no-untyped-def]
    with open(input_file_path, "r") as f:
        sam_template = yaml_parse(f)  # type: ignore[no-untyped-call]

    try:
        cloud_formation_template = transform(sam_template, {}, ManagedPolicyLoader(iam_client))  # type: ignore[no-untyped-call]
        cloud_formation_template_prettified = json.dumps(cloud_formation_template, indent=1)

        with open(output_file_path, "w") as f:
            f.write(cloud_formation_template_prettified)

        print("Wrote transformed CloudFormation template to: " + output_file_path)
    except InvalidDocumentException as e:
        error_message = reduce(lambda message, error: message + " " + error.message, e.causes, e.message)
        LOG.error(error_message)
        errors = map(lambda cause: cause.message, e.causes)
        LOG.error(errors)


def deploy(template_file):  # type: ignore[no-untyped-def]
    capabilities = cli_options.get("--capabilities")
    stack_name = cli_options.get("--stack-name")
    args = ["--template-file", template_file, "--capabilities", capabilities, "--stack-name", stack_name]

    execute_command("deploy", args)  # type: ignore[no-untyped-call]

    return package_output_template_file


if __name__ == "__main__":
    input_file_path, output_file_path = get_input_output_file_paths()  # type: ignore[no-untyped-call]

    if cli_options.get("package"):
        package_output_template_file = package(input_file_path, output_file_path)  # type: ignore[no-untyped-call]
        transform_template(package_output_template_file, output_file_path)  # type: ignore[no-untyped-call]
    elif cli_options.get("deploy"):
        package_output_template_file = package(input_file_path, output_file_path)  # type: ignore[no-untyped-call]
        transform_template(package_output_template_file, output_file_path)  # type: ignore[no-untyped-call]
        deploy(output_file_path)  # type: ignore[no-untyped-call]
    else:
        transform_template(input_file_path, output_file_path)  # type: ignore[no-untyped-call]
