# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
"""
Rackspace driver
"""
from libcloud.compute.base import NodeLocation, VolumeSnapshot
from libcloud.compute.types import Provider, LibcloudError, VolumeSnapshotState
from libcloud.utils.iso8601 import parse_date
from libcloud.common.rackspace import AUTH_URL
from libcloud.compute.drivers.openstack import (
    OpenStack_1_0_Response,
    OpenStack_1_0_Connection,
    OpenStack_1_0_NodeDriver,
    OpenStack_1_1_Connection,
    OpenStack_1_1_NodeDriver,
)

SERVICE_TYPE = "compute"
SERVICE_NAME_GEN1 = "cloudServers"
SERVICE_NAME_GEN2 = "cloudServersOpenStack"
ENDPOINT_ARGS_MAP = {
    "dfw": {"service_type": SERVICE_TYPE, "name": SERVICE_NAME_GEN2, "region": "DFW"},
    "ord": {"service_type": SERVICE_TYPE, "name": SERVICE_NAME_GEN2, "region": "ORD"},
    "iad": {"service_type": SERVICE_TYPE, "name": SERVICE_NAME_GEN2, "region": "IAD"},
    "lon": {"service_type": SERVICE_TYPE, "name": SERVICE_NAME_GEN2, "region": "LON"},
    "syd": {"service_type": SERVICE_TYPE, "name": SERVICE_NAME_GEN2, "region": "SYD"},
    "hkg": {"service_type": SERVICE_TYPE, "name": SERVICE_NAME_GEN2, "region": "HKG"},
}


class RackspaceFirstGenConnection(OpenStack_1_0_Connection):
    """
    Connection class for the Rackspace first-gen driver.
    """

    responseCls = OpenStack_1_0_Response
    XML_NAMESPACE = "http://docs.rackspacecloud.com/servers/api/v1.0"
    auth_url = AUTH_URL
    _auth_version = "2.0"
    cache_busting = True

    def __init__(self, *args, **kwargs):
        self.region = kwargs.pop("region", None)
        super().__init__(*args, **kwargs)

    def get_endpoint(self):
        if "2.0" in self._auth_version:
            ep = self.service_catalog.get_endpoint(
                service_type=SERVICE_TYPE, name=SERVICE_NAME_GEN1
            )
        else:
            raise LibcloudError('Auth version "%s" not supported' % (self._auth_version))

        public_url = ep.url

        if not public_url:
            raise LibcloudError("Could not find specified endpoint")

        # This is a nasty hack, but it's required because of how the
        # auth system works.
        # Old US accounts can access UK API endpoint, but they don't
        # have this endpoint in the service catalog. Same goes for the
        # old UK accounts and US endpoint.
        if self.region == "us":
            # Old UK account, which only have uk endpoint in the catalog
            public_url = public_url.replace("https://lon.servers.api", "https://servers.api")
        elif self.region == "uk":
            # Old US account, which only has us endpoints in the catalog
            public_url = public_url.replace("https://servers.api", "https://lon.servers.api")

        return public_url

    def get_service_name(self):
        return SERVICE_NAME_GEN1


class RackspaceFirstGenNodeDriver(OpenStack_1_0_NodeDriver):
    name = "Rackspace Cloud (First Gen)"
    website = "http://www.rackspace.com"
    connectionCls = RackspaceFirstGenConnection
    type = Provider.RACKSPACE_FIRST_GEN
    api_name = "rackspace"

    def __init__(self, key, secret=None, secure=True, host=None, port=None, region="us", **kwargs):
        """
        @inherits:  :class:`NodeDriver.__init__`

        :param region: Region ID which should be used
        :type region: ``str``
        """
        if region not in ["us", "uk"]:
            raise ValueError("Invalid region: %s" % (region))

        super().__init__(
            key=key,
            secret=secret,
            secure=secure,
            host=host,
            port=port,
            region=region,
            **kwargs,
        )

    def list_locations(self):
        """
        Lists available locations

        Locations cannot be set or retrieved via the API, but currently
        there are two locations, DFW and ORD.

        @inherits: :class:`OpenStack_1_0_NodeDriver.list_locations`
        """
        if self.region == "us":
            locations = [NodeLocation(0, "Rackspace DFW1/ORD1", "US", self)]
        elif self.region == "uk":
            locations = [NodeLocation(0, "Rackspace UK London", "UK", self)]

        return locations

    def _ex_connection_class_kwargs(self):
        kwargs = self.openstack_connection_kwargs()
        kwargs["region"] = self.region
        return kwargs


class RackspaceConnection(OpenStack_1_1_Connection):
    """
    Connection class for the Rackspace next-gen OpenStack base driver.
    """

    auth_url = AUTH_URL
    _auth_version = "2.0"

    def __init__(self, *args, **kwargs):
        self.region = kwargs.pop("region", None)
        self.get_endpoint_args = kwargs.pop("get_endpoint_args", None)
        super().__init__(*args, **kwargs)

    def get_service_name(self):
        if not self.get_endpoint_args:
            # if they used ex_force_base_url, assume the Rackspace default
            return SERVICE_NAME_GEN2

        return self.get_endpoint_args.get("name", SERVICE_NAME_GEN2)

    def get_endpoint(self):
        if not self.get_endpoint_args:
            raise LibcloudError("RackspaceConnection must have get_endpoint_args set")

        if "2.0" in self._auth_version:
            ep = self.service_catalog.get_endpoint(**self.get_endpoint_args)
        else:
            raise LibcloudError('Auth version "%s" not supported' % (self._auth_version))

        public_url = ep.url

        if not public_url:
            raise LibcloudError("Could not find specified endpoint")

        return public_url


class RackspaceNodeDriver(OpenStack_1_1_NodeDriver):
    name = "Rackspace Cloud (Next Gen)"
    website = "http://www.rackspace.com"
    connectionCls = RackspaceConnection
    type = Provider.RACKSPACE

    _networks_url_prefix = "/os-networksv2"

    def __init__(
        self,
        key,
        secret=None,
        secure=True,
        host=None,
        port=None,
        region="dfw",
        **kwargs,
    ):
        """
        @inherits:  :class:`NodeDriver.__init__`

        :param region: ID of the region which should be used.
        :type region: ``str``
        """
        valid_regions = ENDPOINT_ARGS_MAP.keys()

        if region not in valid_regions:
            raise ValueError("Invalid region: %s" % (region))

        if region == "lon":
            self.api_name = "rackspacenovalon"
        elif region == "syd":
            self.api_name = "rackspacenovasyd"
        else:
            self.api_name = "rackspacenovaus"

        super().__init__(
            key=key,
            secret=secret,
            secure=secure,
            host=host,
            port=port,
            region=region,
            **kwargs,
        )

    def _to_snapshot(self, api_node):
        if "snapshot" in api_node:
            api_node = api_node["snapshot"]

        extra = {
            "volume_id": api_node["volumeId"],
            "name": api_node["displayName"],
            "created": api_node["createdAt"],
            "description": api_node["displayDescription"],
            "status": api_node["status"],
        }

        state = self.SNAPSHOT_STATE_MAP.get(api_node["status"], VolumeSnapshotState.UNKNOWN)

        try:
            created_td = parse_date(api_node["createdAt"])
        except ValueError:
            created_td = None

        snapshot = VolumeSnapshot(
            id=api_node["id"],
            driver=self,
            size=api_node["size"],
            extra=extra,
            created=created_td,
            state=state,
            name=api_node["displayName"],
        )
        return snapshot

    def _ex_connection_class_kwargs(self):
        endpoint_args = ENDPOINT_ARGS_MAP[self.region]
        kwargs = self.openstack_connection_kwargs()
        kwargs["region"] = self.region
        kwargs["get_endpoint_args"] = endpoint_args
        return kwargs
