#!/usr/bin/env python
# -*- coding: utf-8 -*-


from __future__ import absolute_import

import six.moves.configparser as ConfigParser
import datetime
import json
import re
import requests
import warnings

try:
    import keyring
except ImportError:
    keyring = None

import pyos
from pyos import exceptions as exc
from .resource import BaseResource
from . import utils as utils


_pat = r"""
        (\d{4})-(\d{2})-(\d{2})     # YYYY-MM-DD
        T                           # Separator
        (\d{2}):(\d{2}):(\d{2})     # HH:MM:SS
        \.\d+                       # Decimal and fractional seconds
        ([\-\+])(\d{2}):(\d{2})     # TZ offset, in ±HH:00 format
        """
_utc_pat = r"""
        (\d{4})-(\d{2})-(\d{2})     # YYYY-MM-DD
        T                           # Separator
        (\d{2}):(\d{2}):(\d{2})     # HH:MM:SS
        \.?\d*                      # Decimal and fractional seconds
        Z                           # UTC indicator
        """
API_DATE_PATTERN = re.compile(_pat, re.VERBOSE)
UTC_API_DATE_PATTERN = re.compile(_utc_pat, re.VERBOSE)
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

# Default region for all services. Can be individually overridden if needed
default_region = None


class Tenant(BaseResource):
    pass


class User(BaseResource):
    pass


class Role(BaseResource):
    pass


class Service(object):
    """
    Represents an available service from the service catalog.
    """
    def __init__(self, identity, catalog):
        """
        Parse the catalog entry for a particular service.
        """
        self.identity = identity
        self.name = catalog.get("name")
        # Replace any dashes with underscores.
        fulltype = catalog["type"].replace("-", "_")
        # Some provider-specific services are prefixed with that info.
        try:
            self.prefix, self.service_type = fulltype.split(":")
        except ValueError:
            self.prefix = ""
            self.service_type = fulltype
        if self.service_type == "compute":
            if self.name.lower() == "cloudservers":
                # First-generation Rackspace cloud servers
                return
        self.clients = {}
        self.endpoints = utils.DotDict()
        eps = catalog.get("endpoints", [])
        for ep in eps:
            rgn = ep.get("region", "ALL")
            self.endpoints[rgn] = Endpoint(ep, self.service_type, rgn, identity)
        return


    def __repr__(self):
        memloc = hex(id(self))
        return "<'%s' Service object at %s>" % (self.service_type, memloc)


    def _ep_for_region(self, region):
        """
        Given a region, returns the Endpoint for that region, or the Endpoint
        for the ALL region if no match is found. If no match is found, None
        is returned, and it is up to the calling method to handle it
        appropriately.
        """
        rgn = region.upper()
        try:
            rgn_ep = [ep for ep in list(self.endpoints.values())
                    if ep.region.upper() == rgn][0]
        except IndexError:
            # See if there is an 'ALL' region.
            try:
                rgn_ep = [ep for ep in list(self.endpoints.values())
                        if ep.region.upper() == "ALL"][0]
            except IndexError:
                rgn_ep = None
        return rgn_ep


    def get_client(self, region):
        """
        Returns an instance of the appropriate client class for the given
        region. If there is no endpoint for that region, a NoEndpointForRegion
        exception is raised.
        """
        ep = self._ep_for_region(region)
        if not ep:
            raise exc.NoEndpointForRegion("There is no endpoint defined for the "
                    "region '%s' for the '%s' service." % (region,
                    self.service_type))
        return ep.client


    @property
    def regions(self):
        """
        Returns a list of all regions which support this service.
        """
        return list(self.endpoints.keys())



class Endpoint(object):
    """
    Holds the endpoint information, as well as an instance of the appropriate
    client for that service and region.
    """
    public_url = None
    private_url = None
    tenant_id = None
    region = None
    _client = None
    _client_private = None
    attr_map = {"publicURL": "public_url",
            "privateURL": "private_url",
            "internalURL": "private_url",
            "tenantId": "tenant_id",
            }


    def __init__(self, ep_dict, service, region, identity):
        """
        Set local attributes from the supplied dictionary.
        """
        self.service = service
        self.region = region
        self.identity = identity
        for key, val in list(ep_dict.items()):
            att_name = self.attr_map.get(key, key)
            setattr(self, att_name, val)


    def get_new_client(self, public=True):
        """
        Returns a new instance of the client for this endpoint.
        """
        return self._get_client(public=public, cached=False)


    def _get_client(self, public=True, cached=True, client_class=None):
        client_att = "_client" if public else "_client_private"
        clt = getattr(self, client_att)
        if isinstance(clt, exc.NoClientForService):
            # Already failed
            raise clt
        if cached and clt is not None:
            if client_class and not isinstance(clt, client_class):
                clt = None
            else:
                return clt
        # Create the client
        special_class = bool(client_class)
        if special_class:
            clt_class = client_class
        else:
            clt_class = pyos.client_class_for_service(self.service)
        if clt_class is None:
            noclass = exc.NoClientForService("No client for the '%s' service "
                    "has been registered." % self.service)
            setattr(self, client_att, noclass)
            raise noclass
        url_att = "public_url" if public else "private_url"
        url = getattr(self, url_att)
        if not url:
            nourl = exc.NoEndpointForService("No %s endpoint is available for "
                    "the '%s' service." % (url_att, self.service))
            setattr(self, client_att, nourl)
            raise nourl
        clt = self._create_client(clt_class, url, public=public,
                special=special_class)
        setattr(self, client_att, clt)
        return clt


    def get(self, url_type):
        """
        Accepts either 'public' or 'private' as a parameter, and returns the
        corresponding value for 'public_url' or 'private_url', respectively.
        """

        lowtype = url_type.lower()
        if lowtype == "public":
            r
eturn self.public_url
        elif lowtype == "private":
            return self.private_url
        else:
            raise ValueError("Valid values are 'public' or 'private'; "
                    "received '%s'." % url_type)


    def __getattr__(self, att):
        clt = self.client
        ret = getattr(clt, att, None)
        if ret:
            return ret
        else:
            raise AttributeError("Endpoint for service '%s' in region '%s' "
                    "has no attribute '%s'." % (self.service, self.region, att))


    @property
    def client(self):
        return self._get_client(public=True)


    @property
    def client_private(self):
        return self._get_client(public=False)


    def _create_client(self, clt_class, url, public=True, special=False):
        """
        Creates a client instance for the service.
        """
        verify_ssl = pyos.get_setting("verify_ssl")
        if self.service == "compute" and not special:
            # Novaclient requires different parameters.
            client = pyos.connect_to_cloudservers(region=self.region,
                    context=self.identity)
            client.identity = self.identity
        else:
            client = clt_class(self.identity, region_name=self.region,
                    management_url=url, verify_ssl=verify_ssl)
        return client



class BaseIdentity(object):
    """
    This class handles all of the bas