# Copyright (c) 2014 Mirantis 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.


import copy
import pprint

import ipaddr
import netaddr

from neutronclient.common import exceptions as neutron_exc
from neutronclient.v2_0 import client as neutron_client

from cloudferrylib.base import exception
from cloudferrylib.base import network
from cloudferrylib.os.identity import keystone as ksresource
from cloudferrylib.utils import cache
from cloudferrylib.utils import log
from cloudferrylib.utils import utils as utl


LOG = log.getLogger(__name__)
DEFAULT_SECGR = 'default'


@cache.Cached(getter='get_subnets_list', modifier='create_network')
@cache.Cached(getter='get_networks_list', modifier='create_network')
@cache.Cached(getter='get_ports_list', modifier='create_port')
class NeutronNetwork(network.Network):

    """
    The main class for working with OpenStack Neutron client
    """

    def __init__(self, config, cloud):
        super(NeutronNetwork, self).__init__(config)
        self.cloud = cloud
        self.identity_client = cloud.resources[utl.IDENTITY_RESOURCE]
        self.filter_tenant_id = None
        self.ext_net_map = \
            utl.read_yaml_file(self.config.migrate.ext_net_map) or {}
        self.mysql_connector = cloud.mysql_connector('neutron')

    @property
    def neutron_client(self):
        return self.proxy(self.get_client(), self.config)

    def get_client(self):

        return neutron_client.Client(
            username=self.config.cloud.user,
            password=self.config.cloud.password,
            tenant_name=self.config.cloud.tenant,
            auth_url=self.config.cloud.auth_url,
            cacert=self.config.cloud.cacert,
            insecure=self.config.cloud.insecure,
            region_name=self.config.cloud.region
        )

    def read_info(self, **kwargs):

        """Get info about neutron resources:
        :rtype: Dictionary with all necessary neutron info
        """

        if kwargs.get('tenant_id'):
            tenant_id = self.filter_tenant_id = kwargs['tenant_id'][0]
        else:
            tenant_id = ''

        nets = self.get_networks(tenant_id)
        subnets = self.get_subnets(tenant_id)
        detached_ports = self.get_detached_ports(tenant_id)
        LOG.debug('List of detached ports: %s',
                  repr([p['id'] for p in detached_ports]))

        if self.filter_tenant_id is not None:
            shared_nets = self.get_shared_networks_raw()
            for net in shared_nets:
                # do not include the same network twice
                if net['id'] in [n['id'] for n in nets]:
                    continue
                nets.append(self.convert_networks(net, self.cloud))
                LOG.debug("Got shared network ID %s", net['id'])
                # Append subnets from the shared networks
                for subnet in net['subnets']:
                    # do not include the same subnets twice
                    if subnet['id'] in [sn['id'] for sn in subnets]:
                        continue
                    subnets.append(self.convert_subnets(subnet, self.cloud))
                    LOG.debug("Got shared subnet ID %s", subnet['id'])

            full_nets_list = self.get_networks()
        else:
            full_nets_list = nets

        # Get full list off busy segmentation IDs
        used_seg_ids = get_segmentation_ids_from_net_list(full_nets_list)

        routers = self.get_routers()
        subnet_ids = {sn['id'] for sn in subnets}
        for router in routers:
            router['subnet_ids'] = [sn_id for sn_id in router['subnet_ids']
                                    if sn_id in subnet_ids]

        info = {'networks': nets,
                'subnets': subnets,
                'routers': routers,
                'detached_ports': detached_ports,
                'floating_ips': self.get_floatingips(tenant_id),
                'security_groups': self.get_sec_gr_and_rules(tenant_id),
                'quota': self.get_quota(tenant_id),
                'meta': {
                    'segmentation_ids': used_seg_ids
                }}
        if self.config.migrate.keep_lbaas:
            info['lbaas'] = dict()
            info['lb_pools'] = self.get_lb_pools(tenant_id)
            info['lb_monitors'] = self.get_lb_monitors(tenant_id)
            info['lb_members'] = self.get_lb_members(tenant_id)
            info['lb_vips'] = self.get_lb_vips(tenant_id)
        return info

    def show_quota(self, tenant_id=''):
        return self.neutron_client.show_quota(tenant_id)

    def list_quotas(self):
        return self.neutron_client.list_quotas()['quotas']

    def get_quota(self, tenant_id):
        # return structure {'name_tenant': {'subnet': 10, ...}, ...}
        tenants = {}
        if not tenant_id:
            tenants_obj = self.identity_client.get_tenants_list()
            tenants = {t.id: t.name for t in tenants_obj}
        else:
            tenants[tenant_id] = self.identity_client.\
                try_get_tenant_name_by_id(tenant_id)
        data = {
        }
        if self.config.network.get_all_quota:
            for t_id, t_val in tenants.iteritems():
                data[t_val] = self.neutron_client.show_quota(t_id)
        else:
            for t in self.neutron_client.list_quotas()['quotas']:
                if (not tenant_id) or (tenant_id == t['tenant_id']):
                    tenant_name = self.identity_client.\
                        try_get_tenant_name_by_id(t['tenant_id'])
                    data[tenant_name] = {k: v
                                         for k, v in t.iteritems()
                                         if k != 'tenant_id'}
        return data

    def upload_quota(self, quota):
        identity = self.identity_client
        for q_name, q_val in quota.iteritems():
            tenant_id = identity.get_tenant_id_by_name(q_name)
            self.neutron_client.update_quota(tenant_id, q_val)

    def create_quota(self, tenant_id, quota):
        return self.neutron_client.update_quota(tenant_id, quota)

    def required_tenants(self, filter_tenant_id=None):
        old_filter_tanant_id = self.filter_tenant_id
        self.filter_tenant_id = filter_tenant_id

        tenant_ids = set()
        for shared_net in self.get_shared_networks_raw():
            tenant_ids.add(shared_net['tenant_id'])
        for router in self.get_routers_raw():
            tenant_ids.add(router['tenant_id'])

        self.filter_tenant_id = old_filter_tanant_id

        return list(tenant_ids)

    def deploy(self, info):
        """
        Deploy network resources to DST

        Have non trivial behavior when enabled kee
p_floatingip and
        change_router_ips. Example:
        Initial state:
            src cloud with router external ip 123.
0.0.5
                and FloatingIP 123.0.0.4
        Migrate resources:
            1. Move FloatingIP to dst. On dst we have FloatingIP 123.0.0.4
            2. Create FloatingIP on dst as stub for router IP.
                On dst we have two FloatingIP [123.0.0.4, 123.0.0.5].
                IP 123.0.0.5 exists only in OpenStack DB and not crush
                src network.
            3. Create router on dst. (here is the main idea) As you see above,
                ips 123.0.0.4 and 123.0.0.5 already allocated,
                then OpenStack must allocate another ip for router
                (e.g. 123.0.0.6).
            4. FloatingIP 123.0.0.5 is not needed anymore.
                We use it on 1.3. step for not allow OpenStack create
                router with this ip. It will be released if you enable
                clean_router_ips_stub in config
        After re