# Copyright (c) 2013 OpenStack Foundation
# 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.

"""
Dns Driver that uses Designate DNSaaS.
"""

from trove.common import cfg
from trove.common import exception
from trove.dns import driver
from trove.openstack.common import log as logging
from designateclient.v1 import Client
from designateclient.v1.records import Record
import base64
import hashlib


CONF = cfg.CONF

DNS_TENANT_ID = CONF.dns_account_id
DNS_AUTH_URL = CONF.dns_auth_url
DNS_ENDPOINT_URL = CONF.dns_endpoint_url
DNS_SERVICE_TYPE = CONF.dns_service_type
DNS_REGION = CONF.dns_region
DNS_USERNAME = CONF.dns_username
DNS_PASSKEY = CONF.dns_passkey
DNS_TTL = CONF.dns_ttl
DNS_DOMAIN_ID = CONF.dns_domain_id
DNS_DOMAIN_NAME = CONF.dns_domain_name


LOG = logging.getLogger(__name__)


class DesignateObjectConverter(object):

    def domain_to_zone(self, domain):
        return DesignateDnsZone(id=domain.id, name=domain.name)

    def record_to_entry(self, record, dns_zone):
        return driver.DnsEntry(name=record.name, content=record.data,
                               type=record.type, ttl=record.ttl,
                               priority=record.priority, dns_zone=dns_zone)


def create_designate_client():
    """Creates a Designate DNSaaS client."""
    client = Client(auth_url=DNS_AUTH_URL,
                    username=DNS_USERNAME,
                    password=DNS_PASSKEY,
                    tenant_id=DNS_TENANT_ID,
                    endpoint=DNS_ENDPOINT_URL,
                    service_type=DNS_SERVICE_TYPE,
                    region_name=DNS_REGION)
    return client


class DesignateDriver(driver.DnsDriver):

    def __init__(self):
        self.dns_client = create_designate_client()
        self.converter = DesignateObjectConverter()
        self.default_dns_zone = DesignateDnsZone(id=DNS_DOMAIN_ID,
                                                 name=DNS_DOMAIN_NAME)

    def create_entry(self, entry, content):
        """Creates the entry in the driver at the given dns zone."""
        dns_zone = entry.dns_zone or self.default_dns_zone
        if not dns_zone.id:
            raise TypeError("The entry's dns_zone must have an ID specified.")
        name = entry.name
        LOG.debug("Creating DNS entry %s." % name)
        client = self.dns_client
        # Record name has to end with a '.' by dns standard
        record = Record(name=entry.name + '.',
                        type=entry.type,
                        data=content,
                        ttl=entry.ttl,
                        priority=entry.priority)
        client.records.create(dns_zone.id, record)

    def delete_entry(self, name, type, dns_zone=None):
        """Deletes an entry with the given name and type from a dns zone."""
        dns_zone = dns_zone or self.default_dns_zone
        records = self._get_records(dns_zone)
        matching_record = [rec for rec in records
                           if rec.name == name + '.' and rec.type == type]
        if not matching_record:
            raise exception.DnsRecordNotFound(name)
        LOG.debug("Deleting DNS entry %s." % name)
        self.dns_client.records.delete(dns_zone.id, matching_record[0].id)

    def get_entries_by_content(self, content, dns_zone=None):
        """Retrieves all entries in a dns_zone with a matching content field"""
        records = self._get_records(dns_zone)
        return [self.converter.record_to_entry(record, dns_zone)
                for record in records if record.data == content]

    def get_entries_by_name(self, name, dns_zone):
        records = self._get_records(dns_zone)
        return [self.converter.record_to_entry(record, dns_zone)
                for record in records if record.name == name]

    def get_dns_zones(self, name=None):
        """Returns all dns zones (optionally filtered by the name argument."""
        domains = self.dns_client.domains.list()
        return [self.converter.domain_to_zone(domain)
                for domain in domains if not name or domain.name == name]

    def modify_content(self, name, content, dns_zone):
        # We dont need this in trove for now
        raise NotImplementedError("Not implemented for Designate DNS.")

    def rename_entry(self, content, name, dns_zone):
        # We dont need this in trove for now
        raise NotImplementedError("Not implemented for Designate DNS.")

    def _get_records(self, dns_zone):
        dns_zone = dns_zone or self.default_dns_zone
        if not dns_zone:
            raise TypeError('DNS domain is must be specified')
        return self.dns_client.records.list(dns_zone.id)


class DesignateInstanceEntryFactory(driver.DnsInstanceEntryFactory):
    """Defines how instance DNS entries are created for instances."""

    def create_entry(self, instance_id):
        zone = DesignateDnsZone(id=DNS_DOMAIN_ID, name=DNS_DOMAIN_NAME)
        # Constructing the hostname by hashing the instance ID.
        name = base64.b32encode(hashlib.md5(instance_id).digest())[:11].lower()
        hostname = ("%s.%s" % (name, zone.name))
        #Removing the leading dot if present
        if hostname.endswith('.'):
            hostname = hostname[:-1]

        return driver.DnsEntry(name=hostname, content=None, type="A",
                               ttl=DNS_TTL, dns_zone=zone)


class DesignateDnsZone(driver.DnsZone):

    def __init__(self, id, name):
        self._name = name
        self._id = id

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def id(self):
        return self._id

    @id.setter
    def id(self, value):
        self._id = value

    def __eq__(self, other):
        return (isinstance(other, DesignateDnsZone) and
                self.name == other.name and
                self.id == other.id)

    def __str__(self):
        return "%s:%s" % (self.id, self.name)
