#
#    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 heat.common import exception
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine import support
from heat.engine import translation


class DesignateZone(resource.Resource):
    """Heat Template Resource for Designate Zone.

    Designate provides DNS-as-a-Service services for OpenStack. So, zone, part
    of domain is a realm with an identification string, unique in DNS.
    """

    support_status = support.SupportStatus(
        version='8.0.0')

    PROPERTIES = (
        NAME, TTL, DESCRIPTION, EMAIL, TYPE, PRIMARIES,
        MASTERS, ATTRS,
    ) = (
        'name', 'ttl', 'description', 'email', 'type', 'primaries',
        'masters', 'attributes',
    )

    ATTRIBUTES = (
        SERIAL,
    ) = (
        'serial',
    )

    TYPES = (
        PRIMARY, SECONDARY
    ) = (
        'PRIMARY', 'SECONDARY'
    )

    properties_schema = {
        # Based on RFC 1035, length of name is set to max of 255
        NAME: properties.Schema(
            properties.Schema.STRING,
            _('DNS Name for the zone.'),
            required=True,
            constraints=[constraints.Length(max=255)]
        ),
        # Based on RFC 1035, range for ttl is set to 1 to signed 32 bit number
        TTL: properties.Schema(
            properties.Schema.INTEGER,
            _('Time To Live (Seconds) for the zone.'),
            update_allowed=True,
            constraints=[constraints.Range(min=1,
                                           max=2147483647)]
        ),
        # designate mandates to the max length of 160 for description
        DESCRIPTION: properties.Schema(
            properties.Schema.STRING,
            _('Description of zone.'),
            update_allowed=True,
            constraints=[constraints.Length(max=160)]
        ),
        EMAIL: properties.Schema(
            properties.Schema.STRING,
            _('E-mail for the zone. Used in SOA records for the zone. '
              'It is required for PRIMARY Type, otherwise ignored.'),
            update_allowed=True,
        ),
        TYPE: properties.Schema(
            properties.Schema.STRING,
            _('Type of zone. PRIMARY is controlled by Designate, SECONDARY '
              'zones are transferred from another DNS Server.'),
            default=PRIMARY,
            constraints=[constraints.AllowedValues(
                allowed=TYPES)]
        ),
        MASTERS: properties.Schema(
            properties.Schema.LIST,
            _('The primary servers to transfer DNS zone information from. '
              'Mandatory for zone type SECONDARY, otherwise ignored.'),
            update_allowed=True,
            support_status=support.SupportStatus(
                status=support.DEPRECATED,
                version='15.0.0',
                message=_('Use ``primaries`` instead.')
            )
        ),
        PRIMARIES: properties.Schema(
            properties.Schema.LIST,
            _('The primary servers to transfer DNS zone information from. '
              'Mandatory for zone type SECONDARY, otherwise ignored.'),
            update_allowed=True
        ),
        ATTRS: properties.Schema(
            properties.Schema.MAP,
            _('Key:Value pairs of information about this zone, '
              'and the pool the user would like to place the zone in. '
              'This information can be used by the scheduler to place '
              'zones on the correct pool.'),
            support_status=support.SupportStatus(version='24.0.0')
        ),
    }

    attributes_schema = {
        SERIAL: attributes.Schema(
            _("DNS zone serial number."),
            type=attributes.Schema.STRING
        ),
    }

    default_client_name = 'designate'

    entity = 'zones'

    def validate(self):
        super(DesignateZone, self).validate()

        def raise_invalid_exception(zone_type, prp):
            if self.properties.get(self.TYPE) == zone_type:
                if not self.properties.get(prp):
                    msg = _('Property %(prp)s is required for zone type '
                            '%(zone_type)s') % {
                        "prp": prp,
                        "zone_type": zone_type
                    }
                    raise exception.StackValidationFailed(message=msg)

        raise_invalid_exception(self.PRIMARY, self.EMAIL)
        raise_invalid_exception(self.SECONDARY, self.MASTERS)

    def translation_rules(self, props):
        return [
            # Translate to "masters" as that is what the Designate API uses,
            # even though we have deprecated that name for the property
            # in favour of "primaries".
            translation.TranslationRule(props,
                                        translation.TranslationRule.REPLACE,
                                        translation_path=[self.MASTERS],
                                        value_name=self.PRIMARIES)
        ]

    def handle_create(self):
        args = dict((k, v) for k, v in self.properties.items() if v)
        args['type_'] = args.pop(self.TYPE)

        zone = self.client().zones.create(**args)

        self.resource_id_set(zone['id'])

    def _check_status_complete(self):
        zone = self.client().zones.get(self.resource_id)

        if zone['status'] == 'ERROR':
            raise exception.ResourceInError(
                resource_status=zone['status'],
                status_reason=_('Error in zone'))

        return zone['status'] != 'PENDING'

    def check_create_complete(self, handler_data=None):
        return self._check_status_complete()

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        args = dict()

        for prp in (self.EMAIL, self.TTL, self.DESCRIPTION, self.MASTERS):
            if prop_diff.get(prp):
                args[prp] = prop_diff.get(prp)

        if len(args.keys()) > 0:
            self.client().zones.update(self.resource_id, args)

    def check_update_complete(self, handler_data=None):
        return self._check_status_complete()

    def _resolve_attribute(self, name):
        if self.resource_id is None:
            return
        if name == self.SERIAL:
            zone = self.client().zones.get(self.resource_id)
            return zone[name]

    def check_delete_complete(self, handler_data=None):
        if not handler_data:
            return True
        try:
            zone = self.client().zones.get(self.resource_id)
        except Exception as exc:
            self.client_plugin().ignore_not_found(exc)
            return True
        if zone["status"] == "ERROR":
            raise exception.ResourceInError(
                resource_status=zone['status'],
                status_reason=_('Error in zone'))
        return False


def resource_mapping():
    return {
        'OS::Designate::Zone': DesignateZone
    }
