# Copyright 2020 AT&T Intellectual Property.  All other rights reserved.
#
# 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 os
from pathlib import Path

from kube_utility_container.kubecfg.kube_cfg import KubeCfg

from kube_utility_container.services.exceptions import \
    KubeApiException
from kube_utility_container.services.exceptions import \
    KubeConfigException
from kube_utility_container.services.exceptions import \
    KubeDeploymentNotFoundException
from kube_utility_container.services.exceptions import \
    KubeEnvVarException
from kube_utility_container.services.exceptions import \
    KubePodNotFoundException

from kubernetes import client as kubeclient
from kubernetes import config as kubeconf

from kubernetes.client.rest import ApiException
from kubernetes.stream import stream

from oslo_log import log as logging
from urllib3.exceptions import MaxRetryError

LOG = logging.getLogger(__name__)


class UtilityContainerClient(object):
    """Client to execute utilscli command on utility containers"""

    NAMESPACE = 'utility'

    def __init__(self):
        # Initialize variables
        self._corev1api_client = None
        self._appsv1api_client = None

    @property
    def _corev1api_api_client(self):
        """Property to get the V1CoreAPI client object"""
        if self._corev1api_client:
            return self._corev1api_client
        else:
            try:
                kubeconf.load_kube_config(config_file=self._kubeconfig_file)
                self._corev1api_client = kubeclient.CoreV1Api()
                return self._corev1api_client
            except EnvironmentError as err:
                LOG.exception(
                    'Failed to load Kubernetes config file: {}'.format(err))
                raise KubeConfigException(err)

    @property
    def _appsv1api_api_client(self):
        """Property to get the V1AppsAPI client object"""
        if self._appsv1api_client:
            return self._appsv1api_client
        else:
            try:
                kubeconf.load_kube_config(config_file=self._kubeconfig_file)
                self._appsv1api_client = kubeclient.AppsV1Api()
                return self._appsv1api_client
            except EnvironmentError as err:
                LOG.exception(
                    'Failed to load Kubernetes config file: {}'.format(err))
                raise KubeConfigException(err)

    @property
    def _kubeconfig_file(self):
        """Property to generate kubeconfig file from environment variables"""
        key = 'KUBECONFIG'
        if os.environ.get(key) is not None:
            kube_conf_filename = os.environ.get(key)
        else:
            raise KubeEnvVarException(key)
        if os.path.isfile(kube_conf_filename):
            return kube_conf_filename
        else:
            self._prepare_kube_config(kube_conf_filename)
            return kube_conf_filename

    def _prepare_kube_config(self, kube_conf_filename):
        """Method to generate the kube config file"""
        Path(Path.cwd() / 'etc').mkdir(exist_ok=True)
        Path(kube_conf_filename).touch()
        conf = KubeCfg(kube_conf_filename)
        region_key = 'OS_REGION_NAME'
        kube_server_key = 'KUBE_SERVER'
        if os.environ.get(region_key) is not None:
            server = os.environ.get(kube_server_key)
        else:
            raise KubeEnvVarException(kube_server_key)

        if os.environ.get(region_key) is not None:
            conf.set_cluster(
                name=os.environ.get(region_key),
                server=server,
                insecure_skip_tls_verify=True)
        else:
            raise KubeEnvVarException(region_key)
        username_key = 'OS_USERNAME'
        if os.environ.get(username_key) is not None:
            conf.set_context(
                name='context_uc',
                user=os.environ.get(username_key),
                namespace='utility',
                cluster=os.environ.get(region_key))
        else:
            raise KubeEnvVarException(username_key)
        conf.use_context('context_uc')
        exec_command_key = 'KUBE_KEYSTONE_AUTH_EXEC'
        if os.environ.get(exec_command_key) is not None:
            conf.set_credentials(
                name=os.environ.get(username_key),
                exec_command=os.environ.get(exec_command_key),
                exec_api_version='client.authentication.k8s.io/v1beta1')
        else:
            raise KubeEnvVarException(exec_command_key)

    def _get_deployment_selectors(self, deployment_name):
        """Method to get the deployment selectors of the deployment queried.

        :param deployment_name: if specified the deployment name of the utility pod
            where the utilscli command is to be executed.
        :type deployment_name: string
            where the utilscli command is to be executed.
        :return: selectors extracted from the deployment
            returned as a string in the format: "key=value, key1=value2..."
        :exception:
            KubeDeploymentNotFoundException -- A custom exception
            KubeDeploymentNotFoundException is raised if no deployment is
            found with the with the parameters namespace and deployment_name
            which is passed as a field_selector.
        """
        # Get a specific deployment by passing the deployment metadata name
        # and the namespace
        deployment = self._appsv1api_api_client.list_namespaced_deployment(
            self.NAMESPACE,
            field_selector='metadata.name={}'.format(deployment_name)).items
        if deployment:
            # Get the selectors from the deployment object returned.
            selector_dict = deployment[0].spec.selector.match_labels
            # Convert the selector dictionary to a string object.
            selectors = ', '.join(
                "{!s}={!s}".format(k, v) for (k, v) in selector_dict.items())
            return selectors
        else:
            raise KubeDeploymentNotFoundException(
                'Deployment with name {} not found in {} namespace'.format(
                    deployment_name, self.NAMESPACE))

    def _get_utility_container(self, deployment_name):
        """Method to get a specific utility container filtered by the selectors

        :param deployment_name: if specified the deployment name of the utility pod
            where the utilscli command is to be executed.
        :type deployment_name: string
            where the utilscli command is to be executed.
        :return: selectors extracted from the deployment
            utility_container {V1Pod} -- Returns the first pod matched.
        :exception: KubePodNotFoundException -- Exception raised if not pods are found.
        """
        deployment_selectors = self._get_deployment_selectors(deployment_name)
        utility_containers = self._corev1api_api_client.list_namespaced_pod(
            self.NAMESPACE, label_selector=deployment_selectors).items
        if utility_containers:
            return utility_containers[0]
        else:
            raise KubePodNotFoundException(
                'No Pods found in Deployment {} with selectors {} in {} '
                'namespace'.format(
                    deployment_name, deployment_selectors, self.NAMESPACE))

    def _get_exec_cmd_output(self, utility_container, ex_cmd, default=1):
        """Exec into a specific utility container, then execute the utilscli
            command and return the output of the command

        :params utility_container: Utility container where the
            utilscli command will be executed.
        :type utility_container: string
        :params ex_cmd: command to be executed inside the utility container
        :type ex_cmd: strings
        :params default: return of cmd_output. optionally can be disabled
        :type integer: 1 (true)
        :type ex_cmd: strings
        :return: Output of command executed in the utility container
        """

        try:
            container = utility_container.spec.containers[0].name
            LOG.info(
                '\nPod Name: {} \nNamespace: {} \nContainer Name: {} '
                '\nCommand: {}'.format(
                    utility_container.metadata.name, self.NAMESPACE, container,
                    ex_cmd))
            cmd_output = stream(
                self._corev1api_api_client.connect_get_namespaced_pod_exec,
                utility_container.metadata.name,
                self.NAMESPACE,
                container=container,
                command=ex_cmd,
                stderr=True,
                stdin=False,
                stdout=True,
                tty=False)
            LOG.info(
                'Pod Name: {} Command Output: {}'.format(
                    utility_container.metadata.name, cmd_output))
            if default is 1:
                return cmd_output
        except (ApiException, MaxRetryError) as err:
            LOG.exception(
                "An exception occurred in pod "
                "exec command: {}".format(err))
            raise KubeApiException(err)

    def exec_cmd(self, deployment_name, cmd):
        """Get specific utility container using deployment name, call

        method to execute utilscli command and return the output of
        the command.

        :params deployment_name: deployment name of the utility pod
            where the utilscli command is to be executed.
        :type deployment_name: string
        :params cmd: command to be executed inside the utility container
        :type cmd: strings
        :return: Output of command executed in the utility container
        """

        utility_container = self._get_utility_container(deployment_name)
        return self._get_exec_cmd_output(utility_container, cmd)