# 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
import random
import string

from oslo_log import log as logging
import six

from senlin.common import context
from senlin.common import exception as exc
from senlin.objects import cluster as cluster_obj
from senlin.profiles.os.nova import server

LOG = logging.getLogger(__name__)


def GenKubeToken():
    token_id = ''.join([random.choice(
        string.digits + string.ascii_lowercase) for i in range(6)])
    token_secret = ''.join([random.choice(
        string.digits + string.ascii_lowercase) for i in range(16)])
    token = '.'.join([token_id, token_secret])
    return token


def loadScript(path):
    script_file = os.path.join(os.path.dirname(__file__), path)
    with open(script_file, "r") as f:
        content = f.read()
    return content


class KubeBaseProfile(server.ServerProfile):
    """Kubernetes Base Profile."""

    def __init__(self, type_name, name, **kwargs):
        super(KubeBaseProfile, self).__init__(type_name, name, **kwargs)
        self.server_id = None

    def _generate_kubeadm_token(self, obj):
        token = GenKubeToken()
        # store generated token

        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        data = obj.data
        data[self.KUBEADM_TOKEN] = token
        cluster_obj.Cluster.update(ctx, obj.id, {'data': data})
        return token

    def _get_kubeadm_token(self, obj):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        if obj.cluster_id:
            cluster = cluster_obj.Cluster.get(ctx, obj.cluster_id)
            return cluster.data.get(self.KUBEADM_TOKEN)
        return None

    def _update_master_ip(self, obj, ip):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        if obj.cluster_id:
            cluster = cluster_obj.Cluster.get(ctx, obj.cluster_id)
            cluster.data['kube_master_ip'] = ip
            cluster.update(ctx, obj.cluster_id, {'data': cluster.data})

    def _create_network(self, obj):
        client = self.network(obj)
        try:
            net = client.network_create()
            subnet = client.subnet_create(network_id=net.id,
                                          cidr='10.7.0.0/24',
                                          ip_version=4)
        except exc.InternalError as ex:
            raise exc.EResourceCreation(type='kubernetes',
                                        message=six.text_type(ex),
                                        resource_id=obj.id)
        pub_net = client.network_get(self.properties[self.PUBLIC_NETWORK])
        try:
            router = client.router_create(
                external_gateway_info={"network_id": pub_net.id})
            client.add_interface_to_router(router, subnet_id=subnet.id)
            fip = client.floatingip_create(floating_network_id=pub_net.id)
        except exc.InternalError as ex:
            raise exc.EResourceCreation(type='kubernetes',
                                        message=six.text_type(ex),
                                        resource_id=obj.id)

        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        data = obj.data
        data[self.PRIVATE_NETWORK] = net.id
        data[self.PRIVATE_SUBNET] = subnet.id
        data[self.PRIVATE_ROUTER] = router.id
        data[self.KUBE_MASTER_FLOATINGIP] = fip.floating_ip_address
        data[self.KUBE_MASTER_FLOATINGIP_ID] = fip.id

        cluster_obj.Cluster.update(ctx, obj.id, {'data': data})

        return net.id

    def _delete_network(self, obj):
        client = self.network(obj)
        fip_id = obj.data.get(self.KUBE_MASTER_FLOATINGIP_ID)
        if fip_id:
            try:
                # delete floating ip
                client.floatingip_delete(fip_id)
            except exc.InternalError as ex:
                raise exc.EResourceDeletion(type='kubernetes', id=fip_id,
                                            message=six.text_type(ex))

        router = obj.data.get(self.PRIVATE_ROUTER)
        subnet = obj.data.get(self.PRIVATE_SUBNET)
        if router and subnet:
            try:
                client.remove_interface_from_router(router, subnet_id=subnet)
            except exc.InternalError as ex:
                raise exc.EResourceDeletion(type='kubernetes',
                                            id=subnet,
                                            message=six.text_type(ex))

        if router:
            try:
                # delete router
                client.router_delete(router, ignore_missing=True)
            except exc.InternalError as ex:
                raise exc.EResourceDeletion(type='kubernetes',
                                            id=router,
                                            message=six.text_type(ex))

        net = obj.data.get(self.PRIVATE_NETWORK)
        if net:
            try:
                # delete network
                client.network_delete(net, ignore_missing=True)
            except exc.InternalError as ex:
                raise exc.EResourceDeletion(type='kubernetes',
                                            id=net,
                                            message=six.text_type(ex))

    def _associate_floatingip(self, obj, server):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)

        if obj.cluster_id:
            cluster = cluster_obj.Cluster.get(ctx, obj.cluster_id)
            fip = cluster.data.get(self.KUBE_MASTER_FLOATINGIP)
            if fip:
                try:
                    self.compute(obj).server_floatingip_associate(server,
                                                                  fip)
                except exc.InternalError as ex:
                    raise exc.EResourceOperation(op='floatingip',
                                                 type='kubernetes',
                                                 id=fip,
                                                 message=six.text_type(ex))

    def _disassociate_floatingip(self, obj, server):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        if obj.cluster_id:
            cluster = cluster_obj.Cluster.get(ctx, obj.cluster_id)
            fip = cluster.data.get(self.KUBE_MASTER_FLOATINGIP)
            if fip:
                try:
                    self.compute(obj).server_floatingip_disassociate(server,
                                                                     fip)
                except exc.InternalError as ex:
                    raise exc.EResourceOperation(op='floatingip',
                                                 type='kubernetes',
                                                 id=fip,
                                                 message=six.text_type(ex))

    def _get_cluster_data(self, obj):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        if obj.cluster_id:
            cluster = cluster_obj.Cluster.get(ctx, obj.cluster_id)
            return cluster.data
        return {}

    def _get_network(self, obj):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        if obj.cluster_id:
            cluster = cluster_obj.Cluster.get(ctx, obj.cluster_id)
            return cluster.data.get(self.PRIVATE_NETWORK)
        return None

    def _create_security_group(self, obj):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        sgid = obj.data.get(self.SECURITY_GROUP, None)
        if sgid:
            return sgid

        client = self.network(obj)
        try:
            sg = client.security_group_create(name=self.name)
        except Exception as ex:
            raise exc.EResourceCreation(type='kubernetes',
                                        message=six.text_type(ex))
        data = obj.data
        data[self.SECURITY_GROUP] = sg.id
        cluster_obj.Cluster.update(ctx, obj.id, {'data': data})
        self._set_security_group_rules(obj, sg.id)

        return sg.id

    def _get_security_group(self, obj):
        ctx = context.get_service_context(user_id=obj.user,
                                          project_id=obj.project)
        if obj.cluster_id:
            cluster = cluster_obj.Cluster.get(ctx, obj.cluster_id)
            return cluster.data.get(self.SECURITY_GROUP)
        return None

    def _set_security_group_rules(self, obj, sgid):
        client = self.network(obj)
        open_ports = {
            'tcp': [22, 80, 8000, 8080, 6443, 8001, 8443, 443,
                    179, 8082, 8086],
            'udp': [8285, 8472],
            'icmp': [None]
        }
        for p in open_ports.keys():
            for port in open_ports[p]:
                try:
                    client.security_group_rule_create(sgid, port, protocol=p)
                except Exception as ex:
                    raise exc.EResourceCreation(type='kubernetes',
                                                message=six.text_type(ex))

    def _delete_security_group(self, obj):
        sgid = obj.data.get(self.SECURITY_GROUP)
        if sgid:
            try:
                self.network(obj).security_group_delete(sgid,
                                                        ignore_missing=True)
            except exc.InternalError as ex:
                raise exc.EResourceDeletion(type='kubernetes',
                                            id=sgid,
                                            message=six.text_type(ex))

    def do_validate(self, obj):
        """Validate if the spec has provided valid info for server creation.

        :param obj: The node object.
        """
        # validate flavor
        flavor = self.properties[self.FLAVOR]
        self._validate_flavor(obj, flavor)

        # validate image
        image = self.properties[self.IMAGE]
        if image is not None:
            self._validate_image(obj, image)

        # validate key_name
        keypair = self.properties[self.KEY_NAME]
        if keypair is not None:
            self._validate_keypair(obj, keypair)

        return True
