# Copyright 2014 VMware, Inc.
# All 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.
#

from oslo_log import log as logging

from vmware_nsx.plugins.nsx_v.vshield import nsxv_edge_cfg_obj


LOG = logging.getLogger(__name__)


class NsxvLoadbalancer(nsxv_edge_cfg_obj.NsxvEdgeCfgObj):

    SERVICE_NAME = 'loadbalancer'

    def __init__(
            self,
            enabled=True,
            enable_service_insertion=False,
            acceleration_enabled=False):
        super(NsxvLoadbalancer, self).__init__()
        self.payload = {
            'enabled': enabled,
            'enableServiceInsertion': enable_service_insertion,
            'accelerationEnabled': acceleration_enabled}
        self.virtual_servers = {}

    def get_service_name(self):
        return self.SERVICE_NAME

    def add_virtual_server(self, virtual_server):
        self.virtual_servers[virtual_server.payload['name']] = virtual_server

    def del_virtual_server(self, name):
        self.virtual_servers.pop(name, None)

    def serializable_payload(self):
        virt_servers = []
        app_profiles = []
        app_rules = []
        pools = []
        monitors = []

        virt_id = 1
        app_prof_id = 1
        app_rule_id = 1
        pool_id = 1
        monitor_id = 1
        member_id = 1

        for virtual_server in self.virtual_servers.values():
            s_virt = virtual_server.payload.copy()
            s_virt['virtualServerId'] = 'virtualServer-%d' % virt_id
            virt_id += 1

            # Setup app profile
            s_app_prof = virtual_server.app_profile.payload.copy()
            s_app_prof['applicationProfileId'] = ('applicationProfile-%d' %
                                                  app_prof_id)
            app_profiles.append(s_app_prof)
            app_prof_id += 1

            # Bind virtual server to app profile
            s_virt['applicationProfileId'] = s_app_prof['applicationProfileId']

            # Setup app rules
            if virtual_server.app_rules.values():
                s_virt['applicationRuleId'] = []
                for app_rule in virtual_server.app_rules.values():
                    s_app_rule = app_rule.payload.copy()
                    s_app_rule['applicationRuleId'] = ('applicationRule-%d' %
                                                       app_rule_id)
                    app_rule_id += 1

                    # Add to LB object, bind to virtual server
                    app_rules.append(s_app_rule)
                    s_virt['applicationRuleId'].append(
                        s_app_rule['applicationRuleId'])

            # Setup pools
            s_pool = virtual_server.default_pool.payload.copy()
            s_pool['poolId'] = 'pool-%d' % pool_id
            pool_id += 1
            pools.append(s_pool)

            # Add pool members
            s_pool['member'] = []
            for member in virtual_server.default_pool.members.values():
                s_m = member.payload.copy()
                s_m['memberId'] = 'member-%d' % member_id
                member_id += 1
                s_pool['member'].append(s_m)

            # Bind pool to virtual server
            s_virt['defaultPoolId'] = s_pool['poolId']

            s_pool['monitorId'] = []
            # Add monitors
            for monitor in virtual_server.default_pool.monitors.values():
                s_mon = monitor.payload.copy()
                s_mon['monitorId'] = 'monitor-%d' % monitor_id
                monitor_id += 1

                s_pool['monitorId'].append(s_mon['monitorId'])

                monitors.append(s_mon)

            virt_servers.append(s_virt)

        payload = self.payload.copy()
        payload['applicationProfile'] = app_profiles
        if app_rules:
            payload['applicationRule'] = app_rules
        payload['monitor'] = monitors
        payload['pool'] = pools
        payload['virtualServer'] = virt_servers
        payload['featureType'] = 'loadbalancer_4.0'

        return payload

    @staticmethod
    def get_loadbalancer(vcns_obj, edge_id):
        edge_lb = nsxv_edge_cfg_obj.NsxvEdgeCfgObj.get_object(
            vcns_obj,
            edge_id,
            '%s/config' % NsxvLoadbalancer.SERVICE_NAME)

        lb_obj = NsxvLoadbalancer(
            edge_lb['enabled'],
            edge_lb['enableServiceInsertion'],
            edge_lb['accelerationEnabled'])

        # Construct loadbalancer objects
        for virt_srvr in edge_lb['virtualServer']:
            v_s = NsxvLBVirtualServer(
                virt_srvr['name'],
                virt_srvr['ipAddress'],
                virt_srvr['port'],
                virt_srvr['protocol'],
                virt_srvr['enabled'],
                virt_srvr['accelerationEnabled'],
                virt_srvr['connectionLimit'])

            # Find application profile objects, attach to virtual server
            for app_prof in edge_lb['applicationProfile']:
                if (virt_srvr['applicationProfileId'] ==
                    app_prof['applicationProfileId']):
                    a_p = NsxvLBAppProfile(
                        app_prof['name'],
                        app_prof['serverSslEnabled'],
                        app_prof['sslPassthrough'],
                        app_prof['template'],
                        app_prof['insertXForwardedFor'])

                    if app_prof.get('persistence'):
                        a_p.set_persistence(
                            True,
                            app_prof['persistence']['method'],
                            app_prof['persistence'].get('cookieName'),
                            app_prof['persistence'].get('cookieMode'),
                            app_prof['persistence'].get('expire'))

                    v_s.set_app_profile(a_p)

            # Find default pool, attach to virtual server
            for pool in edge_lb['pool']:
                if virt_srvr['defaultPoolId'] == pool['poolId']:
                    p = NsxvLBPool(
                        pool['name'],
                        pool['algorithm'],
                        pool['transparent'])

                    # Add pool members to pool
                    for member in pool['member']:
                        m = NsxvLBPoolMember(
                            member['name'],
                            member['ipAddress'],
                            member['port'],
                            member['monitorPort'],
                            member['condition'],
                            member['weight'],
                            member['minConn'],
                            member['maxConn'])

                        p.add_member(m)

                    # Add monitors to pool
                    for mon in edge_lb['monitor']:
                        if mon['monitorId'] in pool['monitorId']:
                            m = NsxvLBMonitor(
                                mon['name'],
                                mon['interval'],
                                mon['maxRetries'],
                                mon['method'],
                                mon['timeout'],
                                mon['type'],
                                mon['url'])

                            p.add_monitor(m)

                    v_s.set_default_pool(p)

            # Add application rules to virtual server
            for rule in edge_lb['applicationRule']:
                if rule['applicationRuleId'] in virt_srvr['applicationRuleId']:
                    r = NsxvLBAppRule(
                        rule['name'],
                        rule['script'])

                    v_s.add_app_rule(r)

            lb_obj.add_virtual_server(v_s)

        return lb_obj


class NsxvLBAppProfile(object):
    def __init__(
            self,
            name,
            server_ssl_enabled=False,
            ssl_pass_through=False,
            template='TCP',
            insert_xff=False,
            client_ssl_cert=None,
            persist=False,
            persist_method='cookie',
            persist_cookie_name='JSESSIONID',
            persist_cookie_mode='insert',
            persist_expire=30):
        self.payload = {
            'name': name,
            'serverSslEnabled': server_ssl_enabled,
            'sslPassthrough': ssl_pass_through,
            'template': template,
            'insertXForwardedFor': insert_xff}

        if persist:
            self.payload['persistence'] = {
                'method': persist_method,
                'expire': persist_expire
            }
            if persist_cookie_mode == 'cookie':
                self.payload['persistence']['cookieMode'] = persist_cookie_mode
                self.payload['persistence']['cookieName'] = persist_cookie_name

        if client_ssl_cert:
            self.payload['clientSsl'] = {
                'clientAuth': 'ignore',
                'serviceCertificate': [client_ssl_cert]
            }

    def set_persistence(
            self,
            persist=False,
            persist_method='cookie',
            persist_cookie_name='JSESSIONID',
            persist_cookie_mode='insert',
            persist_expire=30):

        if persist:
            self.payload['persistence'] = {
                'method': persist_method,
                'expire': persist_expire
            }
            if persist_cookie_mode == 'cookie':
                self.payload['persistence']['cookieMode'] = persist_cookie_mode
                self.payload['persistence']['cookieName'] = persist_cookie_name

        else:
            self.payload.pop('persistence', None)


class NsxvLBAppRule(object):
    def __init__(self, name, script):
        self.payload = {
            'name': name,
            'script': script}


class NsxvLBVirtualServer(object):
    def __init__(
            self,
            name,
            ip_address,
            port=80,
            protocol='HTTP',
            enabled=True,
            acceleration_enabled=False,
            connection_limit=0,
            enable_service_insertion=False):
        self.payload = {
            'name': name,
            'ipAddress': ip_address,
            'port': port,
            'protocol': protocol,
            'enabled': enabled,
            'accelerationEnabled': acceleration_enabled,
            'connectionLimit': connection_limit,
            'enableServiceInsertion': enable_service_insertion}

        self.app_rules = {}
        self.app_profile = None
        self.default_pool = None

    def add_app_rule(self, app_rule):
        self.app_rules[app_rule.payload['name']] = app_rule

    def del_app_rule(self, name):
        self.app_rules.pop(name, None)

    def set_default_pool(self, pool):
        self.default_pool = pool

    def set_app_profile(self, app_profile):
        self.app_profile = app_profile


class NsxvLBMonitor(object):
    def __init__(
            self,
            name,
            interval=10,
            max_retries=3,
            method='GET',
            timeout=15,
            mon_type='http',
            url='/'):
        self.payload = {
            'name': name,
            'interval': interval,
            'maxRetries': max_retries,
            'method': method,
            'timeout': timeout,
            'type': mon_type,
            'url': url}


class NsxvLBPoolMember(object):
    def __init__(
            self,
            name,
            ip_address,
            port,
            monitor_port=None,
            condition='enabled',
            weight=1,
            min_conn=0,
            max_conn=0):

        self.payload = {
            'name': name,
            'ipAddress': ip_address,
            'port': port,
            'monitorPort': monitor_port,
            'condition': condition,
            'weight': weight,
            'minConn': min_conn,
            'maxConn': max_conn}


class NsxvLBPool(object):
    def __init__(
            self,
            name,
            algorithm='round-robin',
            transparent=False):
        self.payload = {
            'name': name,
            'algorithm': algorithm,
            'transparent': transparent}

        self.members = {}
        self.monitors = {}

    def add_member(self, member):
        self.members[member.payload['name']] = member

    def del_member(self, name):
        self.members.pop(name, None)

    def add_monitor(self, monitor):
        self.monitors[monitor.payload['name']] = monitor

    def del_monitor(self, name):
        self.monitors.pop(name, None)
