# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 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.

import json

from neutron.common import exceptions as exception
from neutron.openstack.common import log
from neutron.plugins.vmware.api_client import exception as api_exc
from neutron.plugins.vmware.common import exceptions as nsx_exc
from neutron.plugins.vmware.common import utils
from neutron.plugins.vmware.nsxlib import _build_uri_path
from neutron.plugins.vmware.nsxlib import do_request

HTTP_GET = "GET"
HTTP_POST = "POST"
HTTP_DELETE = "DELETE"
HTTP_PUT = "PUT"

SERVICECLUSTER_RESOURCE = "service-cluster"
LSERVICESNODE_RESOURCE = "lservices-node"
LSERVICESNODEPORT_RESOURCE = "lport/%s" % LSERVICESNODE_RESOURCE
SUPPORTED_METADATA_OPTIONS = ['metadata_proxy_shared_secret']

LOG = log.getLogger(__name__)


def service_cluster_exists(cluster, svc_cluster_id):
    exists = False
    try:
        exists = (
            svc_cluster_id and
            do_request(HTTP_GET,
                       _build_uri_path(SERVICECLUSTER_RESOURCE,
                                       resource_id=svc_cluster_id),
                       cluster=cluster) is not None)
    except exception.NotFound:
        pass
    return exists


def lsn_for_network_create(cluster, network_id):
    lsn_obj = {
        "service_cluster_uuid": cluster.default_service_cluster_uuid,
        "tags": utils.get_tags(n_network_id=network_id)
    }
    return do_request(HTTP_POST,
                      _build_uri_path(LSERVICESNODE_RESOURCE),
                      json.dumps(lsn_obj),
                      cluster=cluster)["uuid"]


def lsn_for_network_get(cluster, network_id):
    filters = {"tag": network_id, "tag_scope": "n_network_id"}
    results = do_request(HTTP_GET,
                         _build_uri_path(LSERVICESNODE_RESOURCE,
                                         fields="uuid",
                                         filters=filters),
                         cluster=cluster)['results']
    if not results:
        raise exception.NotFound()
    elif len(results) == 1:
        return results[0]['uuid']


def lsn_delete(cluster, lsn_id):
    do_request(HTTP_DELETE,
               _build_uri_path(LSERVICESNODE_RESOURCE,
                               resource_id=lsn_id),
               cluster=cluster)


def lsn_port_host_entries_update(
    cluster, lsn_id, lsn_port_id, conf, hosts_data):
    hosts_obj = {'hosts': hosts_data}
    do_request(HTTP_PUT,
               _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                               parent_resource_id=lsn_id,
                               resource_id=lsn_port_id,
                               extra_action=conf),
               json.dumps(hosts_obj),
               cluster=cluster)


def lsn_port_create(cluster, lsn_id, port_data):
    port_obj = {
        "ip_address": port_data["ip_address"],
        "mac_address": port_data["mac_address"],
        "tags": utils.get_tags(n_mac_address=port_data["mac_address"],
                               n_subnet_id=port_data["subnet_id"]),
        "type": "LogicalServicesNodePortConfig",
    }
    return do_request(HTTP_POST,
                      _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                                      parent_resource_id=lsn_id),
                      json.dumps(port_obj),
                      cluster=cluster)["uuid"]


def lsn_port_delete(cluster, lsn_id, lsn_port_id):
    return do_request(HTTP_DELETE,
                      _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                                      parent_resource_id=lsn_id,
                                      resource_id=lsn_port_id),
                      cluster=cluster)


def _lsn_port_get(cluster, lsn_id, filters):
    results = do_request(HTTP_GET,
                         _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                                         parent_resource_id=lsn_id,
                                         fields="uuid",
                                         filters=filters),
                         cluster=cluster)['results']
    if not results:
        raise exception.NotFound()
    elif len(results) == 1:
        return results[0]['uuid']


def lsn_port_by_mac_get(cluster, lsn_id, mac_address):
    filters = {"tag": mac_address, "tag_scope": "n_mac_address"}
    return _lsn_port_get(cluster, lsn_id, filters)


def lsn_port_by_subnet_get(cluster, lsn_id, subnet_id):
    filters = {"tag": subnet_id, "tag_scope": "n_subnet_id"}
    return _lsn_port_get(cluster, lsn_id, filters)


def lsn_port_info_get(cluster, lsn_id, lsn_port_id):
    result = do_request(HTTP_GET,
                        _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                                        parent_resource_id=lsn_id,
                                        resource_id=lsn_port_id),
                        cluster=cluster)
    for tag in result['tags']:
        if tag['scope'] == 'n_subnet_id':
            result['subnet_id'] = tag['tag']
            break
    return result


def lsn_port_plug_network(cluster, lsn_id, lsn_port_id, lswitch_port_id):
    patch_obj = {
        "type": "PatchAttachment",
        "peer_port_uuid": lswitch_port_id
    }
    try:
        do_request(HTTP_PUT,
                   _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                                   parent_resource_id=lsn_id,
                                   resource_id=lsn_port_id,
                                   is_attachment=True),
                   json.dumps(patch_obj),
                   cluster=cluster)
    except api_exc.Conflict:
        # This restriction might be lifted at some point
        msg = (_("Attempt to plug Logical Services Node %(lsn)s into "
                 "network with port %(port)s failed. PatchAttachment "
                 "already exists with another port") %
               {'lsn': lsn_id, 'port': lswitch_port_id})
        LOG.exception(msg)
        raise nsx_exc.LsnConfigurationConflict(lsn_id=lsn_id)


def _lsn_configure_action(
    cluster, lsn_id, action, is_enabled, obj):
    lsn_obj = {"enabled": is_enabled}
    lsn_obj.update(obj)
    do_request(HTTP_PUT,
               _build_uri_path(LSERVICESNODE_RESOURCE,
                               resource_id=lsn_id,
                               extra_action=action),
               json.dumps(lsn_obj),
               cluster=cluster)


def _lsn_port_configure_action(
    cluster, lsn_id, lsn_port_id, action, is_enabled, obj):
    do_request(HTTP_PUT,
               _build_uri_path(LSERVICESNODE_RESOURCE,
                               resource_id=lsn_id,
                               extra_action=action),
               json.dumps({"enabled": is_enabled}),
               cluster=cluster)
    do_request(HTTP_PUT,
               _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                               parent_resource_id=lsn_id,
                               resource_id=lsn_port_id,
                               extra_action=action),
               json.dumps(obj),
               cluster=cluster)


def lsn_port_dhcp_configure(
        cluster, lsn_id, lsn_port_id, is_enabled=True, dhcp_options=None):
    dhcp_options = dhcp_options or {}
    opts = ["%s=%s" % (key, val) for key, val in dhcp_options.iteritems()]
    dhcp_obj = {
        'options': {'options': opts}
    }
    _lsn_port_configure_action(
        cluster, lsn_id, lsn_port_id, 'dhcp', is_enabled, dhcp_obj)


def lsn_metadata_configure(
        cluster, lsn_id, is_enabled=True, metadata_info=None):
    opts = [
        "%s=%s" % (opt, metadata_info[opt])
        for opt in SUPPORTED_METADATA_OPTIONS
        if metadata_info.get(opt)
    ]
    meta_obj = {
        'metadata_server_ip': metadata_info['metadata_server_ip'],
        'metadata_server_port': metadata_info['metadata_server_port'],
        'misc_options': opts
    }
    _lsn_configure_action(
        cluster, lsn_id, 'metadata-proxy', is_enabled, meta_obj)


def _lsn_port_host_action(
    cluster, lsn_id, lsn_port_id, host_obj, extra_action, action):
    do_request(HTTP_POST,
               _build_uri_path(LSERVICESNODEPORT_RESOURCE,
                               parent_resource_id=lsn_id,
                               resource_id=lsn_port_id,
                               extra_action=extra_action,
                               filters={"action": action}),
               json.dumps(host_obj),
               cluster=cluster)


def lsn_port_dhcp_host_add(cluster, lsn_id, lsn_port_id, host_data):
    _lsn_port_host_action(
        cluster, lsn_id, lsn_port_id, host_data, 'dhcp', 'add_host')


def lsn_port_dhcp_host_remove(cluster, lsn_id, lsn_port_id, host_data):
    _lsn_port_host_action(
        cluster, lsn_id, lsn_port_id, host_data, 'dhcp', 'remove_host')


def lsn_port_metadata_host_add(cluster, lsn_id, lsn_port_id, host_data):
    _lsn_port_host_action(
        cluster, lsn_id, lsn_port_id, host_data, 'metadata-proxy', 'add_host')


def lsn_port_metadata_host_remove(cluster, lsn_id, lsn_port_id, host_data):
    _lsn_port_host_action(cluster, lsn_id, lsn_port_id,
                          host_data, 'metadata-proxy', 'remove_host')
