#!/usr/bin/env python
# Copyright 2015-2016 Yelp Inc.
#
# 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.
"""Contains methods used by the paasta client to wait for deployment
of a docker image to a cluster.instance.
"""
from __future__ import absolute_import
from __future__ import unicode_literals

import logging

from paasta_tools.cli.cmds.mark_for_deployment import NoInstancesFound
from paasta_tools.cli.cmds.mark_for_deployment import wait_for_deployment
from paasta_tools.cli.utils import lazy_choices_completer
from paasta_tools.cli.utils import list_deploy_groups
from paasta_tools.cli.utils import list_services
from paasta_tools.cli.utils import NoSuchService
from paasta_tools.cli.utils import validate_full_git_sha
from paasta_tools.cli.utils import validate_given_deploy_groups
from paasta_tools.cli.utils import validate_service_name
from paasta_tools.remote_git import list_remote_refs
from paasta_tools.utils import _log
from paasta_tools.utils import DEFAULT_SOA_DIR
from paasta_tools.utils import get_git_url
from paasta_tools.utils import paasta_print
from paasta_tools.utils import PaastaColors
from paasta_tools.utils import TimeoutError


DEFAULT_DEPLOYMENT_TIMEOUT = 3600  # seconds


log = logging.getLogger(__name__)


class GitShaError(Exception):
    pass


class DeployGroupError(Exception):
    pass


def add_subparser(subparsers):
    list_parser = subparsers.add_parser(
        'wait-for-deployment',
        help='Wait a service to be deployed to deploy_group',
        description=(
            "'paasta wait-for-deployment' waits for a previously marked for "
            "deployment service to be deployed to deploy_group."
        ),
        epilog=(
            "Note: Access and credentials to the Git repo of a service "
            "are required for this command to work."
        )
    )
    list_parser.add_argument(
        '-u', '--git-url',
        help=('Git url for service. Defaults to the normal git URL for '
              'the service.'),
        default=None
    )
    list_parser.add_argument(
        '-c', '-k', '--commit',
        help='Git sha to wait for deployment',
        required=True,
        type=validate_full_git_sha
    )
    list_parser.add_argument(
        '-l', '--deploy-group',
        help='deploy group (e.g. cluster1.canary, cluster2.main).',
        required=True,
    ).completer = lazy_choices_completer(list_deploy_groups)
    list_parser.add_argument(
        '-s', '--service',
        help='Name of the service which you wish to wait for deployment. '
        'Leading "services-" will be stripped.',
        required=True,
    ).completer = lazy_choices_completer(list_services)
    list_parser.add_argument(
        '-t', '--timeout',
        dest="timeout",
        type=int,
        default=DEFAULT_DEPLOYMENT_TIMEOUT,
        help=(
            "Time in seconds to wait for paasta to deploy the service. "
            "If the timeout is exceeded we return 1. "
            "Default is %(default)s seconds."
        ),
    )
    list_parser.add_argument(
        '-d', '--soa-dir',
        dest="soa_dir",
        metavar="SOA_DIR",
        default=DEFAULT_SOA_DIR,
        help="define a different soa config directory",
    )
    list_parser.add_argument(
        '-v', '--verbose',
        action='count',
        dest="verbose",
        default=0,
        help="Print out more output."
    )

    list_parser.set_defaults(command=paasta_wait_for_deployment)


def get_latest_marked_sha(git_url, deploy_group):
    """Return the latest marked for deployment git sha or ''"""
    refs = list_remote_refs(git_url)
    last_ref = ''
    for ref in refs:
        if (ref.startswith('refs/tags/paasta-{}-'.format(deploy_group)) and
                ref.endswith('-deploy')) and ref > last_ref:
            last_ref = ref
    return refs[last_ref] if last_ref else ''


def validate_git_sha(git_sha, git_url, deploy_group, service):
    """Verify if git_sha is the latest sha marked for deployment.

    Raise exception when the provided git_sha is not the latest
    marked for deployment in 'deploy_group' for 'service'.
    """
    marked_sha = get_latest_marked_sha(git_url, deploy_group)
    if marked_sha == '':
        raise GitShaError("ERROR: Nothing is marked to deployment "
                          "in {} for {}"
                          .format(deploy_group, service))
    if git_sha != marked_sha:
        raise GitShaError("ERROR: The latest git SHA marked for "
                          "deployment in {} is {}"
                          .format(deploy_group, marked_sha))


def validate_deploy_group(deploy_group, service, soa_dir):
    """Validate deploy_group.

    Raise exception if the specified deploy group is not used anywhere.
    """
    in_use_deploy_groups = list_deploy_groups(service=service,
                                              soa_dir=soa_dir)
    _, invalid_deploy_groups = \
        validate_given_deploy_groups(in_use_deploy_groups, [deploy_group])

    if len(invalid_deploy_groups) == 1:
        raise DeployGroupError("ERROR: These deploy groups are not currently "
                               "used anywhere: {0}.\n"
                               "You probably need one of these in-use deploy "
                               "groups?:\n   {1}"
                               .format(",".join(invalid_deploy_groups),
                                       ",".join(in_use_deploy_groups)))


def paasta_wait_for_deployment(args):
    """Wrapping wait_for_deployment"""
    if args.verbose:
        log.setLevel(level=logging.DEBUG)
    else:
        log.setLevel(level=logging.INFO)

    service = args.service
    if service and service.startswith('services-'):
        service = service.split('services-', 1)[1]

    if args.git_url is None:
        args.git_url = get_git_url(service=service, soa_dir=args.soa_dir)

    try:
        validate_service_name(service, soa_dir=args.soa_dir)
        validate_deploy_group(args.deploy_group, service, args.soa_dir)
        validate_git_sha(args.commit, args.git_url,
                         args.deploy_group, service)
    except (GitShaError, DeployGroupError, NoSuchService) as e:
        paasta_print(PaastaColors.red('{}'.format(e)))
        return 1

    try:
        wait_for_deployment(service=service,
                            deploy_group=args.deploy_group,
                            git_sha=args.commit,
                            soa_dir=args.soa_dir,
                            timeout=args.timeout)
        _log(service=service,
             component='deploy',
             line=("Deployment of {0} for {1} complete".
                   format(args.commit, args.deploy_group)),
             level='event')

    except (KeyboardInterrupt, TimeoutError):
        paasta_print("Waiting for deployment aborted.")
        return 1
    except NoInstancesFound:
        return 1

    return 0
