# Copyright 2017 The Armada Authors.
#
# 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.

import grpc
import yaml

from hapi.chart.config_pb2 import Config
from hapi.services.tiller_pb2 import GetReleaseContentRequest
from hapi.services.tiller_pb2 import GetReleaseStatusRequest
from hapi.services.tiller_pb2 import GetVersionRequest
from hapi.services.tiller_pb2 import InstallReleaseRequest
from hapi.services.tiller_pb2 import ListReleasesRequest
from hapi.services.tiller_pb2_grpc import ReleaseServiceStub
from hapi.services.tiller_pb2 import TestReleaseRequest
from hapi.services.tiller_pb2 import UninstallReleaseRequest
from hapi.services.tiller_pb2 import UpdateReleaseRequest
from oslo_config import cfg
from oslo_log import log as logging

from armada.const import STATUS_DEPLOYED, STATUS_FAILED
from armada.exceptions import tiller_exceptions as ex
from armada.handlers.k8s import K8s
from armada.utils.release import release_prefix
from armada.utils.release import label_selectors

TILLER_VERSION = b'2.7.2'
TILLER_TIMEOUT = 300
GRPC_EPSILON = 60
RELEASE_LIMIT = 128  # TODO(mark-burnett): There may be a better page size.
RUNTEST_SUCCESS = 9

# the standard gRPC max message size is 4MB
# this expansion comes at a performance penalty
# but until proper paging is supported, we need
# to support a larger payload as the current
# limit is exhausted with just 10 releases
MAX_MESSAGE_LENGTH = 429496729

CONF = cfg.CONF
LOG = logging.getLogger(__name__)


class TillerResult(object):
    '''Object to hold Tiller results for Armada.'''
    def __init__(self, release, namespace, status, description, version):
        self.release = release
        self.namespace = namespace
        self.status = status
        self.description = description
        self.version = version


class Tiller(object):
    '''
    The Tiller class supports communication and requests to the Tiller Helm
    service over gRPC
    '''

    def __init__(self, tiller_host=None, tiller_port=None,
                 tiller_namespace=None):
        self.tiller_host = tiller_host
        self.tiller_port = tiller_port or CONF.tiller_port
        self.tiller_namespace = tiller_namespace or CONF.tiller_namespace
        # init k8s connectivity
        self.k8s = K8s()

        # init Tiller channel
        self.channel = self.get_channel()

        # init timeout for all requests
        # and assume eventually this will
        # be fed at runtime as an override
        self.timeout = TILLER_TIMEOUT

        LOG.debug('Armada is using Tiller at: %s:%s, namespace=%s, timeout=%s',
                  self.tiller_host, self.tiller_port, self.tiller_namespace,
                  self.timeout)

    @property
    def metadata(self):
        '''
        Return Tiller metadata for requests
        '''
        return [(b'x-helm-api-client', TILLER_VERSION)]

    def get_channel(self):
        '''
        Return a Tiller channel
        '''
        tiller_ip = self._get_tiller_ip()
        tiller_port = self._get_tiller_port()
        try:
            LOG.debug('Tiller getting gRPC insecure channel at %s:%s '
                      'with options: [grpc.max_send_message_length=%s, '
                      'grpc.max_receive_message_length=%s]',
                      tiller_ip, tiller_port,
                      MAX_MESSAGE_LENGTH, MAX_MESSAGE_LENGTH)
            return grpc.insecure_channel(
                '%s:%s' % (tiller_ip, tiller_port),
                options=[
                    ('grpc.max_send_message_length', MAX_MESSAGE_LENGTH),
                    ('grpc.max_receive_message_length', MAX_MESSAGE_LENGTH)
                ]
            )
        except Exception:
            raise ex.ChannelException()

    def _get_tiller_pod(self):
        '''
        Returns Tiller pod using the Tiller pod labels specified in the Armada
        config
        '''
        pods = None
        namespace = self._get_tiller_namespace()
        pods = self.k8s.get_namespace_pod(namespace,
                                          CONF.tiller_pod_labels).items
        # No Tiller pods found
        if not pods:
            raise ex.TillerPodNotFoundException(CONF.tiller_pod_labels)

        # Return first Tiller pod in running state
        for pod in pods:
            if pod.status.phase == 'Running':
                LOG.debug('Found at least one Running Tiller pod.')
                return pod

        # No Tiller pod found in running state
        raise ex.TillerPodNotRunningException()

    def _get_tiller_ip(self):
        '''
        Returns the Tiller pod's IP address by searching all namespaces
        '''
        if self.tiller_host:
            LOG.debug('Using Tiller host IP: %s', self.tiller_host)
            return self.tiller_host
        else:
            pod = self._get_tiller_pod()
            LOG.debug('Using Tiller pod IP: %s', pod.status.pod_ip)
            return pod.status.pod_ip

    def _get_tiller_port(self):
        '''Stub method to support arbitrary ports in the future'''
        LOG.debug('Using Tiller host port: %s', self.tiller_port)
        return self.tiller_port