#    Copyright 2015, eBay 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.

from __future__ import absolute_import

from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext_lazy as _

from horizon import messages

from openstack_dashboard.api import neutron

neutronclient = neutron.neutronclient


class LBDetails(neutron.NeutronAPIDictWrapper):
    """Wrapper for neutron load balancer vip."""

    def __init__(self, vip, listener=None, pool=None, members=None,
                 monitors=None, profile_name=None, cert=None, key=None,
                 chain=None):
        vip['pool'] = pool
        pool['members'] = members
        pool['monitors'] = monitors
        # vip['cert_name'] = cert_name
        vip['listener'] = listener
        vip['cert'] = cert
        vip['key'] = key
        vip['chain'] = chain
        vip['profile_name'] = profile_name
        super(LBDetails, self).__init__(vip)

    class AttributeDict(dict):

        def __getattr__(self, attr):
            return self[attr]

        def __setattr__(self, attr, value):
            self[attr] = value

    def convert_status(self, value):
        return "Enabled" if value else "Disabled"

    def readable(self, request=None):

        pFormatted = {'id': self.id,
                      'name': self.name,
                      'dns_name': self.name,
                      # 'lb_method': self.lb_method,
                      'description': self.description,
                      # 'protocol': self.protocol,
                      'address': self.vip_address,
                      # 'port': self.port,
                      'enabled': self.convert_status(self.admin_state_up),
                      'use_common_cert': False,
                      'provisioning_status': self.provisioning_status,
                      'operating_status': self.operating_status,
                      # 'monitor' : self.monitor
                      }

        # status_string = 'vip: %s' % self['status'].lower()
        pFormatted['status'] = 'na'

        # if self.profile_name:
        #     try:
        #         if self.profile_name.upper() ==
        #          _construct_common_cert_profile_name(request).upper():
        #             pFormatted['use_common_cert'] = True
        #             pFormatted['cert_name'] = self.profile_name
        #         else:
        #             pFormatted['use_common_cert'] = False
        #             pFormatted['cert_name'] = self.profile_name
        #             pFormatted['cert'] = self.cert
        #             pFormatted['private_key'] = self.key
        #             pFormatted['chain_cert'] = self.chain
        #     except Exception as e:
        #         LOG.error("unable to read cert")

        if self.listener is not None:
            try:
                listener = self.AttributeDict(self.listener)
                pFormatted['protocol'] = listener.protocol
                pFormatted['port'] = listener.protocol_port
            except Exception:
                pass

        if self.pool is not None:
            try:
                pool = self.AttributeDict(self.pool)
                # status_string = '%s \n pool: %s' % (pFormatted['status'],
                # pool['status'].lower())
                # pFormatted['status'] = status_string
                pFormatted['pool'] = pool
                pFormatted['pool_id'] = pool.id
                pFormatted['lb_method'] = pool.lb_algorithm

                if pool.members is not None:
                    try:
                        ips = []
                        pool_members = []
                        for m in pool.members:
                            member = self.AttributeDict(m)
                            pFormatted['instance_port'] = member.protocol_port

                            pool_member = member
                            pool_member.port = member.protocol_port
                            pool_members.append(pool_member)

                            ips.append(member.address)
                        pFormatted['pool']['members'] = pool_members
                        pFormatted['members'] = ips
                    except Exception:
                        pass  # ignore

                if pool.monitors is not None:
                    try:
                        for m in pool.monitors:
                            monitor = self.AttributeDict(m)
                            # monitor_status =_get_monitor_status(pool['id'],m)
                            # status_string = '%s \n monitor: %s' %
                            # (pFormatted['status'], monitor_status.lower())
                            # pFormatted['status'] = status_string
                            interval = int(monitor.delay)
                            # timeout = int(monitor.timeout)
                            retry = int(monitor.max_retries)
                            monitor_type = 'http-ecv'
                            # if monitor.name.upper() in basic_monitors:
                            # monitor_type = monitor.name
                            monitor_type = monitor.name
                            pFormatted['pool']['monitor'] = monitor_type
                            pFormatted['monitor'] = monitor_type
                            pFormatted['interval'] = interval
                            pFormatted['timeout'] = retry
                            pFormatted['send'] = monitor.url_path \
                                if hasattr(monitor, 'url_path') else ''
                            pFormatted['receive'] = monitor.response_string \
                                if hasattr(monitor, 'response_string') else ''
                            break
                    except Exception:
                        pass  # ignore
            except Exception:
                pass  # ignore

        if 'cert_name' not in pFormatted:
            pFormatted['cert_name'] = ''
        if 'cert' not in pFormatted:
            pFormatted['cert'] = ''
        if 'private_key' not in pFormatted:
            pFormatted['private_key'] = ''
        if 'chain_cert' not in pFormatted:
            pFormatted['chain_cert'] = ''
        if 'pool_id' not in pFormatted:
            pFormatted['pool_id'] = 'UNKNOWN'
        if 'lb_method' not in pFormatted:
            pFormatted['lb_method'] = 'UNKNOWN'
        if 'monitor' not in pFormatted:
            pFormatted['monitor'] = 'None'
        # if 'monitor' not in pFormatted['pool']:
        #     pFormatted['pool']['monitor'] = 'None'
        if 'interval' not in pFormatted:
            pFormatted['interval'] = 1
        if 'timeout' not in pFormatted:
            pFormatted['timeout'] = 1
        if 'send' not in pFormatted:
            pFormatted['send'] = None
        if 'receive' not in pFormatted:
            pFormatted['receive'] = None
        if 'members' not in pFormatted:
            pFormatted['members'] = []
        if 'instance_port' not in pFormatted:
            pFormatted['instance_port'] = ''

        return self.AttributeDict(pFormatted)


class Vip(neutron.NeutronAPIDictWrapper):
    """Wrapper for neutron load balancer vip."""

    def __init__(self, apiresource):
        super(Vip, self).__init__(apiresource)


class Pool(neutron.NeutronAPIDictWrapper):
    """Wrapper for neutron load balancer pool."""

    def __init__(self, apiresource):
        if 'provider' not in apiresource:
            apiresource['provider'] = None
        super(Pool, self).__init__(apiresource)


class Member(neutron.NeutronAPIDictWrapper):
    """Wrapper for neutron load balancer member."""

    def __init__(self, apiresource):
        super(Member, self).__init__(apiresource)


class PoolStats(neutron.NeutronAPIDictWrapper):
    """Wrapper for neutron load balancer pool stats."""

    def __init__(self, apiresource):
        super(PoolStats, self).__init__(apiresource)


class PoolMonitor(neutron.NeutronAPIDictWrapper):
    """Wrapper for neutron load balancer pool health monitor."""

    def __init__(self, apiresource):
        super(PoolMonitor, self).__init__(apiresource)


def vip_create(request, **kwargs):
    """Create a vip for a specified pool.

    :param request: request context
    :param address: virtual IP address
    :param name: name for vip
    :param description: description for vip
    :param subnet_id: subnet_id for subnet of vip
    :param protocol_port: transport layer port number for vip
    :returns: Vip object
    """
    body = {'vip': {'name': kwargs['name'],
                    'description': kwargs['description'],
                    'subnet_id': kwargs['subnet_id'],
                    'protocol_port': kwargs['protocol_port'],
                    'protocol': kwargs['protocol'],
                    'pool_id': kwargs['pool_id'],
                    'session_persistence': kwargs['session_persistence'],
                    'admin_state_up': kwargs['admin_state_up']
                    }}
    if kwargs.get('connection_limit'):
        body['vip']['connection_limit'] = kwargs['connection_limit']

    if kwargs.get('address'):
        body['vip']['address'] = kwargs['address']

    vip = neutronclient(request).create_vip(body).get('vip')
    return Vip(vip)


def vip_list(request, **kwargs):
    vips = neutronclient(request).list_vips(**kwargs).get('vips')
    return [Vip(v) for v in vips]


def create_loadbalancer_full(request, **kwargs):
    loadbalancer_body = {'loadbalancer': {'name': kwargs['name'],
                                          'description': kwargs['description'],
                                          'vip_subnet_id': kwargs['subnet_id'],
                                          'admin_state_up':
                                          kwargs['admin_state_up'],
                                          'vip_address': kwargs['address'],
                                          # 'provider': 'HAProxy'
                                          }}

    listener_body = {'listener': {'name': kwargs['name'],
                                  'description': kwargs['description'],
                                  'protocol': kwargs['protocol'],
                                  'protocol_port': kwargs['protocol_port'],
                                  'default_tls_container_id': None,
                                  'sni_container_ids': [],
                                  'connection_limit': 100,
                                  'admin_state_up': kwargs['admin_state_up'],
                                  'loadbalancer_id': None}}

    pool_body = {'pool': {'name': kwargs['name'],
                          'description': kwargs['description'],
                          'protocol': kwargs['protocol'],
                          'lb_method': kwargs['lb_method'],
                          'admin_state_up': kwargs['admin_state_up']
                          }}

    member_body = {'member': {'pool_id': kwargs['pool_id'],
                              'address': kwargs['address'],
                              'protocol_port': kwargs['protocol_port'],
                              'admin_state_up': kwargs['admin_state_up'],
                              'pool_id': None
                              }}
    if kwargs.get('weight'):
        member_body['member']['weight'] = kwargs['weight']

    monitor_type = kwargs['type'].upper()

    health_monitor_body = {'health_monitor': {'tenant_id': kwargs['tenant_id'],
                                              'type': monitor_type,
                                              'delay': kwargs['delay'],
                                              'timeout': kwargs['timeout'],
                                              'max_retries':
                                              kwargs['max_retries'],
                                              'admin_state_up':
                                              kwargs['admin_state_up'],
                                              'pool_id': None
                                              }
                           }

    if monitor_type in ['HTTP', 'HTTPS']:
        health_mon = health_monitor_body['health_monitor']
        health_mon['http_method'] = kwargs['http_method']
        health_mon['url_path'] = kwargs['url_path']
        health_mon['expected_codes'] = kwargs['expected_codes']

    try:
        client = neutronclient(request)
        loadbalancer = client.\
            create_loadbalancer(loadbalancer_body).get('loadbalancer')
        listener_body['listener']['loadbalancer_id'] = loadbalancer['id']
        listener = client.\
            create_listener(listener_body).get('listener')
        pool = client.create_lbaas_pool(pool_body).get('pool')
        member_body['member']['pool_id'] = pool['id']
        health_monitor_body['health_monitor']['pool_id'] = pool['id']
        health_monitor = client.create_lbaas_healthmonitor(health_monitor_body)\
            .get('health_monitor')
        member = client.create_lbaas_member(member_body).get('member')
    except Exception:
        raise Exception(_("Could not create full loadbalancer."))
    return [LBDetails(loadbalancer, listener, pool, member, health_monitor)]


def list_loadbalancers(request, **kwargs):
    vips = neutronclient(request).list_loadbalancers(**kwargs)
    vips = [] if not vips else vips
    vips = vips.get('loadbalancers')
    lbaas_list = []

    for vip in vips:
        listeners = vip.get('listeners')
        listeners = [] if not listeners else listeners

        for listener in listeners:
            listener = neutronclient(request).show_listener(listener.get('id'),
                                                            **kwargs)
            if not listener:
                continue
            listener = listener.get('listener')

            try:
                pool = neutronclient(request).\
                    show_lbaas_pool(listener.get('default_pool_id'), **kwargs)
                if not pool:
                    continue
                pool = pool.get('pool')
                if pool.get('healthmonitor_id'):
                    health_monitor = neutronclient(request).\
                        show_lbaas_healthmonitor(pool.get('healthmonitor_id'),
                                                 **kwargs)
                    health_monitor = health_monitor.get('healthmonitor')
                else:
                    health_monitor = None
                members = neutronclient(request).\
                    list_lbaas_members(listener.get('default_pool_id'),
                                       **kwargs)
                lbaas_list.append(LBDetails(vip, listener, pool,
                                            members, health_monitor))
            except Exception:
                raise Exception(_("Could not get load balancer list."))
    return lbaas_list


def show_loadbalancer(request, lbaas_loadbalancer, **kwargs):
    vip = neutronclient(request).show_loadbalancer(lbaas_loadbalancer,
                                                   **kwargs)
    if not vip:
        return
    loadbalancer = vip.get('loadbalancer')
    viplisteners = loadbalancer.get('listeners')
    if not viplisteners:
        return
    for viplistener in viplisteners:
        listener = neutronclient(request).\
            show_listener(viplistener.get('id'), **kwargs)
        if not listener:
            continue
        listener = listener.get('listener')
        pool = neutronclient(request).\
            show_lbaas_pool(listener.get('default_pool_id'), **kwargs)
        if not pool:
            continue
        pool = pool.get('pool')
        health_monitor = None
        if pool.get('healthmonitor_id'):
            health_monitor = neutronclient(request).\
                show_lbaas_healthmonitor(pool.get('healthmonitor_id'),
                                         **kwargs)
            health_monitor = health_monitor.get('healthmonitor')
        members = neutronclient(request).\
            list_lbaas_members(listener.get('default_pool_id'), **kwargs)
    return LBDetails(vip.get('loadbalancer'), listener, pool, members,
                     health_monitor)


def vip_get(request, vip_id):
    return _vip_get(request, vip_id, expand_resource=True)


def _vip_get(request, vip_id, expand_resource=False):
    vip = neutronclient(request).show_vip(vip_id).get('vip')
    if expand_resource:
        vip['subnet'] = neutron.subnet_get(request, vip['subnet_id'])
        vip['port'] = neutron.port_get(request, vip['port_id'])
        vip['pool'] = _pool_get(request, vip['pool_id'])
    return Vip(vip)


def vip_update(request, vip_id, **kwargs):
    vip = neutronclient(request).update_vip(vip_id, kwargs).get('vip')
    return Vip(vip)


def vip_delete(request, vip_id):
    neutronclient(request).delete_vip(vip_id)


def pool_create(request, **kwargs):
    """Create a pool for specified protocol

    :param request: request context
    :param name: name for pool
    :param description: description for pool
    :param subnet_id: subnet_id for subnet of pool
    :param protocol: load balanced protocol
    :param lb_method: load balancer method
    :param admin_state_up: admin state (default on)
    """
    body = {'pool': {'name': kwargs['name'],
                     'description': kwargs['description'],
                     'subnet_id': kwargs['subnet_id'],
                     'protocol': kwargs['protocol'],
                     'lb_method': kwargs['lb_method'],
                     'admin_state_up': kwargs['admin_state_up'],
                     'provider': kwargs['provider'],
                     }}
    pool = neutronclient(request).create_pool(body).get('pool')
    return Pool(pool)


def _get_vip(request, pool, vip_dict, expand_name_only=False):
    if pool['vip_id'] is not None:
        try:
            if vip_dict:
                vip = vip_dict.get(pool['vip_id'])
            else:
                vip = _vip_get(request, pool['vip_id'])
        except Exception:
            messages.warning(request, _("Unable to get VIP for pool "
                                        "%(pool)s.") % {"pool": pool["id"]})
            vip = Vip({'id': pool['vip_id'], 'name': ''})
        if expand_name_only:
            vip = vip.name_or_id
        return vip
    else:
        return None


def pool_list(request, **kwargs):
    return _pool_list(request, expand_subnet=True, expand_vip=True, **kwargs)


def _pool_list(request, expand_subnet=False, expand_vip=False, **kwargs):
    pools = neutronclient(request).list_pools(**kwargs).get('pools')
    if expand_subnet:
        subnets = neutron.subnet_list(request)
        subnet_dict = SortedDict((s.id, s) for s in subnets)
        for p in pools:
            subnet = subnet_dict.get(p['subnet_id'])
            p['subnet_name'] = subnet.cidr if subnet else None
    if expand_vip:
        vips = vip_list(request)
        vip_dict = SortedDict((v.id, v) for v in vips)
        for p in pools:
            p['vip_name'] = _get_vip(request, p, vip_dict,
                                     expand_name_only=True)
    return [Pool(p) for p in pools]


def pool_get(request, pool_id):
    return _pool_get(request, pool_id, expand_resource=True)


def _pool_get(request, pool_id, expand_resource=False):
    try:
        pool = neutronclient(request).show_pool(pool_id).get('pool')
    except Exception:
        messages.warning(request, _("Unable to get pool detail."))
        return None
    if expand_resource:
        # TODO(lyj): The expand resource(subnet, member etc.) attached
        # to a pool could be deleted without cleanup pool related database,
        # this will cause exceptions if we trying to get the deleted resources.
        # so we need to handle the situation by showing a warning message here.
        # we can safely remove the try/except once the neutron bug is fixed
        # https://bugs.launchpad.net/neutron/+bug/1406854
        try:
            pool['subnet'] = neutron.subnet_get(request, pool['subnet_id'])
        except Exception:
            messages.warning(request, _("Unable to get subnet for pool "
                                        "%(pool)s.") % {"pool": pool_id})
        pool['vip'] = _get_vip(request, pool, vip_dict=None,
                               expand_name_only=False)
        try:
            pool['members'] = _member_list(request, expand_pool=False,
                                           pool_id=pool_id)
        except Exception:
            messages.warning(request, _("Unable to get members for pool "
                                        "%(pool)s.") % {"pool": pool_id})
        try:
            pool['health_monitors'] = pool_health_monitor_list(
                request, id=pool['health_monitors'])
        except Exception:
            messages.warning(request,
                             _("Unable to get health monitors "
                               "for pool %(pool)s.") % {"pool": pool_id})
    return Pool(pool)


def pool_update(request, pool_id, **kwargs):
    pool = neutronclient(request).update_pool(pool_id, kwargs).get('pool')
    return Pool(pool)


def pool_delete(request, pool):
    neutronclient(request).delete_pool(pool)


# not linked to UI yet
def pool_stats(request, pool_id, **kwargs):
    stats = neutronclient(request).retrieve_pool_stats(pool_id, **kwargs)
    return PoolStats(stats)


def pool_health_monitor_create(request, **kwargs):
    """Create a health monitor

    :param request: request context
    :param type: type of monitor
    :param delay: delay of monitor
    :param timeout: timeout of monitor
    :param max_retries: max retries [1..10]
    :param http_method: http method
    :param url_path: url path
    :param expected_codes: http return code
    :param admin_state_up: admin state
    """
    monitor_type = kwargs['type'].upper()
    body = {'health_monitor': {'type': monitor_type,
                               'delay': kwargs['delay'],
                               'timeout': kwargs['timeout'],
                               'max_retries': kwargs['max_retries'],
                               'admin_state_up': kwargs['admin_state_up']
                               }}
    if monitor_type in ['HTTP', 'HTTPS']:
        body['health_monitor']['http_method'] = kwargs['http_method']
        body['health_monitor']['url_path'] = kwargs['url_path']
        body['health_monitor']['expected_codes'] = kwargs['expected_codes']
    mon = neutronclient(request).create_health_monitor(body).get(
        'health_monitor')

    return PoolMonitor(mon)


def pool_health_monitor_list(request, **kwargs):
    monitors = neutronclient(request).list_health_monitors(
        **kwargs).get('health_monitors')
    return [PoolMonitor(m) for m in monitors]


def pool_health_monitor_get(request, monitor_id):
    return _pool_health_monitor_get(request, monitor_id, expand_resource=True)


def _pool_health_monitor_get(request, monitor_id, expand_resource=False):
    monitor = neutronclient(request
                            ).show_health_monitor(monitor_id
                                                  ).get('health_monitor')
    if expand_resource:
        pool_ids = [p['pool_id'] for p in monitor['pools']]
        monitor['pools'] = _pool_list(request, id=pool_ids)
    return PoolMonitor(monitor)


def pool_health_monitor_update(request, monitor_id, **kwargs):
    monitor = neutronclient(request).update_health_monitor(monitor_id, kwargs)
    return PoolMonitor(monitor)


def pool_health_monitor_delete(request, mon_id):
    neutronclient(request).delete_health_monitor(mon_id)


def member_create(request, **kwargs):
    """Create a load balance member

    :param request: request context
    :param pool_id: pool_id of pool for member
    :param address: IP address
    :param protocol_port: transport layer port number
    :param weight: weight for member
    :param admin_state_up: admin_state
    """
    body = {'member': {'pool_id': kwargs['pool_id'],
                       'address': kwargs['address'],
                       'protocol_port': kwargs['protocol_port'],
                       'admin_state_up': kwargs['admin_state_up']
                       }}
    if kwargs.get('weight'):
        body['member']['weight'] = kwargs['weight']
    member = neutronclient(request).create_member(body).get('member')
    return Member(member)


def member_list(request, **kwargs):
    return _member_list(request, expand_pool=True, **kwargs)


def _member_list(request, expand_pool, **kwargs):
    members = neutronclient(request).list_members(**kwargs).get('members')
    if expand_pool:
        pools = _pool_list(request)
        pool_dict = SortedDict((p.id, p) for p in pools)
        for m in members:
            m['pool_name'] = pool_dict.get(m['pool_id']).name_or_id
    return [Member(m) for m in members]


def member_get(request, member_id):
    return _member_get(request, member_id, expand_pool=True)


def _member_get(request, member_id, expand_pool):
    member = neutronclient(request).show_member(member_id).get('member')
    if expand_pool:
        member['pool'] = _pool_get(request, member['pool_id'])
    return Member(member)


def member_update(request, member_id, **kwargs):
    member = neutronclient(request).update_member(member_id, kwargs)
    return Member(member)


def member_delete(request, mem_id):
    neutronclient(request).delete_member(mem_id)


def pool_monitor_association_create(request, **kwargs):
    """Associate a health monitor with pool

    :param request: request context
    :param monitor_id: id of monitor
    :param pool_id: id of pool
    """

    body = {'health_monitor': {'id': kwargs['monitor_id'], }}

    neutronclient(request).associate_health_monitor(
        kwargs['pool_id'], body)


def pool_monitor_association_delete(request, **kwargs):
    """Disassociate a health monitor from pool

    :param request: request context
    :param monitor_id: id of monitor
    :param pool_id: id of pool
    """

    neutronclient(request).disassociate_health_monitor(
        kwargs['pool_id'], kwargs['monitor_id'])
