#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# This file is part of Kargo.
#
#    Kargo is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Foobar is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Foobar.  If not, see <http://www.gnu.org/licenses/>.

__version__ = '0.4.7'

import os
import argparse
import getpass
try:
    from ansible.utils.display import Display
except ImportError:
    raise ImportError('Cannot find Ansible: Please check your installation (required version 2)')
from kargo.common import clone_kargo_git_repo
from kargo.configure import Config
from kargo.inventory import CfgInventory
from kargo.deploy import RunPlaybook
from kargo.cloud import AWS, GCE, OpenStack
display = Display()


def prepare(options):
    clone_kargo_git_repo(options)
    Cfg = CfgInventory(options, 'metal')
    Cfg.write_inventory(
        options['masters_list'],
        options['nodes_list'],
        options['etcds_list']
    )


def aws(options):
    clone_kargo_git_repo(options)
    A = AWS(options)
    A.gen_ec2_playbook()
    A.create_instances()
    A.write_inventory()


def gce(options):
    clone_kargo_git_repo(options)
    G = GCE(options)
    G.gen_gce_playbook()
    G.create_instances()
    G.write_inventory()


def openstack(options):
    clone_kargo_git_repo(options)
    O = OpenStack(options)
    O.gen_openstack_playbook()
    O.create_instances()
    O.write_inventory()


def deploy(options):
    Run = RunPlaybook(options)
    Run.ssh_prepare()
    Run.deploy_kubernetes()

if __name__ == '__main__':
    # Main parser
    parser = argparse.ArgumentParser(
        prog='kargo',
        description='%(prog)s Kubernetes cluster deployment tool',
        add_help=False
    )
    subparsers = parser.add_subparsers(help='commands')

    parser.add_argument(
        '-v', '--version', action='version',
        version='%(prog)s'+' %s' % __version__
    )
    # Options shared by the first step subparsers (prepare, gce, aws)
    firststep_parser = argparse.ArgumentParser(add_help=False)
    firststep_parser.add_argument(
        '--noclone', default=False, action='store_true', dest='noclone',
        help='Do not clone the git repo. Useful when the repo is already downloaded'
    )

    # Options shared by all subparsers
    parent_parser = argparse.ArgumentParser(add_help=False)
    parent_parser.add_argument(
        '-p', '--path', dest='kargo_path',
        help='Where the Ansible playbooks are installed. Default: ~/.kargo'
    )
    parent_parser.add_argument(
        '--config', dest='configfile',
        help="Config file path. Defaults to /etc/kargo/kargo.yml"
    )
    parent_parser.add_argument(
        '-y', '--assumeyes', default=False, dest='assume_yes', action='store_true',
        help='When a yes/no prompt would be presented, assume that the user entered "yes"'
    )
    parent_parser.add_argument(
        '-i', '--inventory', dest='inventory_path',
        help='Inventory file path. Defaults to <path parameter>/inventory/inventory.cfg'
    )

    # prepare
    prepare_parser = subparsers.add_parser(
        'prepare', parents=[parent_parser, firststep_parser],
        help='generate inventory and create vms on cloud providers'
    )
    prepare_parser.add_argument('--add', dest='add_node', action='store_true',
        help="Add node to an existing cluster"
    )
    prepare_parser.add_argument(
        '--etcds', dest='etcds_list', metavar='N', nargs='+', default=[],
        help='Number of etcd, these instances will just act as etcd members'
    )
    prepare_parser.add_argument(
        '--masters', dest='masters_list', metavar='N', nargs='+', default=[],
        help='Number of masters, these instances will not run workloads, master components only'
    )
    prepare_parser.add_argument(
        '--nodes', dest='nodes_list', metavar='N', nargs='+',
        required=True, help='List of nodes'
    )
    prepare_parser.set_defaults(func=prepare)

    # aws
    aws_parser = subparsers.add_parser(
        'aws', parents=[parent_parser, firststep_parser],
        help='Create AWS instances and generate inventory'
    )
    aws_parser.add_argument(
        '--access-key', dest='aws_access_key', help='AWS access key'
    )
    aws_parser.add_argument(
        '--secret-key', dest='aws_secret_key', help='AWS secret key'
    )
    aws_parser.add_argument(
        '--masters-instance-type', dest='masters_instance_type',
        help='AWS instance type for Masters (default: t2.medium)'
    )
    aws_parser.add_argument(
        '--nodes-instance-type', dest='nodes_instance_type',
        help='AWS instance type for Nodes (default: t2.large)'
    )
    aws_parser.add_argument(
        '--etcds-instance-type', dest='etcds_instance_type',
        help='AWS instance type for Etcd members (default: t2.small)'
    )
    aws_parser.add_argument(
        '--keypair', dest='key_name', help='AWS key pair name'
    )
    aws_parser.add_argument('--region', dest='region', help='AWS region')
    sg_group = aws_parser.add_mutually_exclusive_group()
    sg_group.add_argument(
        '--security-group-name', dest='security_group_name',
        help='AWS security group Name'
    )
    sg_group.add_argument(
        '--security-group-id', dest='security_group_id',
        help='AWS security group ID. Uses the "default" security group if not specified.'
    )
    aws_parser.add_argument(
        '--assign-public-ip', default=None, dest='assign_public_ip', action='store_true',
         help='when provisioning within vpc, assign a public IP address'
    )
    aws_parser.add_argument(
        '--use-private-ip', default=None, dest='use_private_ip', action='store_true',
         help='If set to true, using the private ip for SSH connection'
    )
    aws_parser.add_argument('--vpc-id', dest='aws_vpc_id', help='EC2 VPC ID')
    aws_parser.add_argument(
        '--vpc-subnet', dest='vpc_subnet_id', help='EC2 VPC subnet ID'
    )
    aws_parser.add_argument('--ami', dest='ami', help='AWS AMI')
    aws_parser.add_argument(
        '--cluster-name', dest='cluster_name', help='Name of the cluster'
    )
    aws_parser.add_argument(
        '--tags', dest='tags', help='List of VM tags of the form \'name=value\'', nargs="+", metavar="NAME=VALUE"
    )
    aws_parser.add_argument('--add', dest='add_node', action='store_true',
        help="Add node to an existing cluster")
    aws_parser.add_argument(
        '--etcd', dest='etcds_count', type=int,
        help='Number of etcd, these instances will just act as etcd members'
    )
    aws_parser.add_argument(
        '--masters', dest='masters_count', type=int,
        help='Number of masters, these instances will not run workloads, master components only'
    )
    aws_parser.add_argument(
        '--nodes', dest='nodes_count', type=int,
        help='Number of nodes', required=True
    )
    aws_parser.set_defaults(func=aws)

    # gce
    gce_parser = subparsers.add_parser(
        'gce', parents=[parent_parser, firststep_parser],
        help='Create GCE machines and generate inventory'
    )
    gce_parser.add_argument(
        '--pem_file', dest='pem_file', help='GCE ssh pem file path'
    )
    gce_parser.add_argument(
        '--credentials_file', dest='credentials_file',
        help='GCE credentials json file path'
    )
    gce_parser.add_argument(
        '--zone', dest='zone', help='GCE zone'
    )
    gce_parser.add_argument(
        '--masters-machine-type', dest='masters_machine_type',
        help='GCE machine type for Masters (default: n1-standard-2)'
    )
    gce_parser.add_argument(
        '--nodes-machine-type', dest='nodes_machine_type',
        help='GCE machine type for Nodes (default: n1-standard-4)'
    )
    gce_parser.add_argument(
        '--etcds-machine-type', dest='etcds_machine_type',
        help='GCE machine type for Etcd members (default: n1-standard-1)'
    )
    gce_parser.add_argument('--image', dest='image', help='GCE image')
    gce_parser.add_argument(
        '--project', dest='project_id', help='GCE project ID'
    )
    gce_parser.add_argument(
        '--email', dest='service_account_email', help='GCE project ID'
    )
    gce_parser.add_argument(
        '--cluster-name', dest='cluster_name', help='Name of the cluster'
    )
    gce_parser.add_argument(
        '--tags', dest='tags', help='List of VM tags', nargs="+", metavar="TAG"
    )
    gce_parser.add_argument('--add', dest='add_node', action='store_true',
        help="Add node to an existing cluster")
    gce_parser.add_argument(
        '--etcd', dest='etcds_count', type=int,
        help='Number of etcd, these instances will just act as etcd members'
    )
    gce_parser.add_argument(
        '--masters', dest='masters_count', type=int,
        help='Number of masters, these instances will not run workloads, master components only'
    )
    gce_parser.add_argument(
        '--nodes', dest='nodes_count', type=int,
        help='Number of nodes', required=True
    )
    gce_parser.set_defaults(func=gce)

    # openstack
    openstack_parser = subparsers.add_parser(
        'openstack', parents=[parent_parser, firststep_parser],
        help='Create OpenStack instances and generate inventory'
    )
    openstack_parser.add_argument(
        '--os_auth_url', dest='os_auth_url', help='OpenStack authentication URL'
    )
    openstack_parser.add_argument(
        '--os_username', dest='os_username', help='OpenStack Username'
    )
    openstack_parser.add_argument(
        '--os_project_name', dest='os_project_name',
        help='OpenStack Project Name'
    )
    openstack_parser.add_argument(
        '--os_password', dest='os_password', help='OpenStack Password'
    )
    openstack_parser.add_argument(
        '--etcd', dest='etcds_count', type=int,
        help='Number of etcd, these instances will just act as etcd members'
    )
    openstack_parser.add_argument(
        '--masters', dest='masters_count', type=int,
        help='Number of masters, these instances will not run workloads, master components only'
    )
    openstack_parser.add_argument(
        '--nodes', dest='nodes_count', type=int,
        help='Number of nodes', required=True
    )
    openstack_parser.add_argument(
        '--sshkey', dest='sshkey', help='Name of saved SSH Keypair'
    )
    openstack_parser.add_argument(
        '-N', '--kube-network', dest='kube_network', default='10.233.0.0/16',
        help="""Network to be used inside the cluster (/16),
             (must not overlap with any of your infrastructure networks and should be the same as in deploy).
             default: 10.233.0.0/16"""
    )
    openstack_parser.add_argument(
        '--add', dest='add_node', action='store_true', default=False,
        help='Add node to an existing cluster')
    openstack_parser.add_argument(
        '--network', dest='network', help='Neutron network name'
    )
    openstack_parser.add_argument(
        '--masters-flavor', dest='masters_flavor',
        help='OpenStack instance flavor for Masters'
    )
    openstack_parser.add_argument(
        '--nodes-flavor', dest='nodes_flavor',
        help='OpenStack instance flavor for Nodes'
    )
    openstack_parser.add_argument(
        '--etcds-flavor', dest='etcds_flavor',
        help='OpenStack instance flavor for Etcd members'
    )
    openstack_parser.add_argument('--image', dest='image', help='Instance boot image')
    openstack_parser.add_argument(
        '--floating_ip', dest='floating_ip', action='store_true',
        required=False, help='Associate public IP to instances'
    )
    openstack_parser.add_argument(
        '--cluster-name', dest='cluster_name', help='Name of the cluster'
    )
    openstack_parser.set_defaults(func=openstack)

    # deploy
    deploy_parser = subparsers.add_parser(
        'deploy', parents=[parent_parser],
        help='Create GCE machines and generate inventory'
    )
    deploy_parser.add_argument(
        '--verbose', default=False, action='store_true',
        help="Run Ansible playbook in verbose mode '-vvvv'"
    )
    deploy_parser.add_argument(
        '-k', '--sshkey', dest='ssh_key',
        help='ssh key for authentication on remote servers'
    )
    deploy_parser.add_argument(
        '-u', '--user', dest='ansible_user', default=getpass.getuser(),
        help='Ansible SSH user (remote user)'
    )
    deploy_parser.add_argument(
        '--passwd', dest='k8s_passwd',
        help="Set the 'kube' passwd to authenticate to the API (default changeme')"
    )
    deploy_parser.add_argument(
        '-P', '--prompt-passwd', default=False, action='store_true', dest='prompt_pwd',
        help="Set the 'kube' passwd to authenticate to the API (Interactive mode)"
    )
    deploy_parser.add_argument(
        '-V', '--kube-version', dest='kube_version',
        help='Choose the kubernetes version to be installed'
    )
    deploy_parser.add_argument(
        '-N', '--kube-network', dest='kube_network',
        help="""Network to be used inside the cluster (/16),
             (must not overlap with any of your infrastructure networks).
             default: 10.233.0.0/16"""
    )
    deploy_parser.add_argument(
        '-n', '--network-plugin',
        choices=['flannel', 'weave', 'calico'],
        help='Inventory defaults to flannel'
    )
    deploy_parser.add_argument(
        '--aws', default=False, action='store_true',
        help='Kubernetes deployment on AWS'
    )
    deploy_parser.add_argument(
        '--gce', default=False, action='store_true',
        help='Kubernetes deployment on GCE'
    )
    deploy_parser.add_argument(
        '--coreos', default=False, action='store_true',
        help='bootstrap python on CoreOS'
    )
    deploy_parser.add_argument(
        '--ansible-opts', dest='ansible_opts',
        help='Ansible options'
    )
    deploy_parser.set_defaults(func=deploy)

    # Parse arguments
    args = parser.parse_args()
    if args.configfile is None:
        default_config_dir = os.path.join(os.path.expanduser("~"), ".config")
        if not os.path.isdir(default_config_dir):
            os.makedirs(default_config_dir)
        args.configfile = os.path.join(default_config_dir, "kargo.yml")
    # Read configfile and update options dict
    C = Config(args.configfile)
    configfile_content = C.parse_configfile
    config = C.default_values(args, configfile_content)
    # Run functions with all the options
    os.environ['ANSIBLE_FORCE_COLOR'] = 'true'
    args.func(config)
