#
# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
#

from __future__ import (
    absolute_import,
    division,
)

from ipalib import (
    _,
    ngettext,
    api,
    Str,
    DNSNameParam,
    output,
    messages
)
from ipalib.errors import DependentEntry
from ipalib.plugable import Registry
from ipaserver.dns_data_management import IPASystemRecords
from ipaserver.plugins.baseldap import (
    LDAPCreate,
    LDAPSearch,
    LDAPRetrieve,
    LDAPDelete,
    LDAPObject,
    LDAPUpdate,
)
from ipapython.dn import DN
from ipapython.dnsutil import DNSName

__doc__ = _("""
IPA locations
""") + _("""
Manipulate DNS locations
""") + _("""
EXAMPLES:
""") + _("""
  Find all locations:
    ipa location-find
""") + _("""
  Show specific location:
    ipa location-show location
""") + _("""
  Add location:
    ipa location-add location --description 'My location'
""") + _("""
  Delete location:
    ipa location-del location
""")

register = Registry()


@register()
class location(LDAPObject):
    """
    IPA locations
    """
    container_dn = api.env.container_locations
    object_name = _('location')
    object_name_plural = _('locations')
    object_class = ['top', 'ipaLocationObject']
    search_attributes = ['idnsName']
    default_attributes = [
        'idnsname', 'description'
    ]
    label = _('IPA Locations')
    label_singular = _('IPA Location')

    permission_filter_objectclasses = ['ipaLocationObject']
    managed_permissions = {
        'System: Read IPA Locations': {
            'ipapermright': {'read', 'search', 'compare'},
            'ipapermdefaultattr': {
                'objectclass', 'idnsname', 'description',
            },
            'default_privileges': {'DNS Administrators'},
        },
        'System: Add IPA Locations': {
            'ipapermright': {'add'},
            'default_privileges': {'DNS Administrators'},
        },
        'System: Remove IPA Locations': {
            'ipapermright': {'delete'},
            'default_privileges': {'DNS Administrators'},
        },
        'System: Modify IPA Locations': {
            'ipapermright': {'write'},
            'ipapermdefaultattr': {
                'description',
            },
            'default_privileges': {'DNS Administrators'},
        },
    }

    takes_params = (
        DNSNameParam(
            'idnsname',
            cli_name='name',
            primary_key=True,
            label=_('Location name'),
            doc=_('IPA location name'),
            # dns name must be relative, we will put it into middle of
            # location domain name for location records
            only_relative=True,
        ),
        Str(
            'description?',
            label=_('Description'),
            doc=_('IPA Location description'),
        ),
        Str(
            'servers_server*',
            label=_('Servers'),
            doc=_('Servers that belongs to the IPA location'),
            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
        ),
        Str(
            'dns_server*',
            label=_('Advertised by servers'),
            doc=_('List of servers which advertise the given location'),
            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
        ),
    )

    def get_dn(self, *keys, **options):
        loc = keys[0]
        assert isinstance(loc, DNSName)
        loc_a = loc.ToASCII()

        return super(location, self).get_dn(loc_a, **options)


@register()
class location_add(LDAPCreate):
    __doc__ = _('Add a new IPA location.')

    msg_summary = _('Added IPA location "%(value)s"')


@register()
class location_del(LDAPDelete):
    __doc__ = _('Delete an IPA location.')

    msg_summary = _('Deleted IPA location "%(value)s"')

    def pre_callback(self, ldap, dn, *keys, **options):
        assert isinstance(dn, DN)
        if not options.get('force'):
            servers = self.api.Command.server_find(
                in_location=keys[-1])['result']
            location_member = servers[0]['cn'][0] if servers else None
            if location_member:
                raise DependentEntry(
                    label=_('IPA Server'),
                    key=keys[-1],
                    dependent=location_member
                )
        system_records =IPASystemRecords(self.api)
        _success, failed = system_records.remove_location_records(keys[-1])
        if failed:
            self.add_message(messages.AutomaticDNSRecordsUpdateFailed())
        return dn


@register()
class location_mod(LDAPUpdate):
    __doc__ = _('Modify information about an IPA location.')

    msg_summary = _('Modified IPA location "%(value)s"')


@register()
class location_find(LDAPSearch):
    __doc__ = _('Search for IPA locations.')

    msg_summary = ngettext(
        '%(count)d IPA location matched',
        '%(count)d IPA locations matched', 0
    )


@register()
class location_show(LDAPRetrieve):
    __doc__ = _('Display information about an IPA location.')

    has_output = LDAPRetrieve.has_output + (
        output.Output(
            'servers',
            type=dict,
            doc=_('Servers in location'),
            flags={'no_display'},  # we use customized print to CLI
        ),
    )

    def execute(self, *keys, **options):
        result = super(location_show, self).execute(*keys, **options)

        servers_additional_info = {}
        if not options.get('raw'):
            servers_name = []
            dns_servers = []
            weight_sum = 0

            servers = self.api.Command.server_find(
                in_location=keys[0], no_members=False)['result']
            for server in servers:
                s_name = server['cn'][0]
                servers_name.append(s_name)
                weight = int(server.get('ipaserviceweight', [100])[0])
                weight_sum += weight
                servers_additional_info[s_name] = {
                    'cn': server['cn'],
                    'ipaserviceweight': server.get(
                        'ipaserviceweight', [u'100']),
                }

                s_roles = server.get('enabled_role_servrole', ())
                if s_roles:
                    servers_additional_info[s_name][
                        'enabled_role_servrole'] = s_roles
                if 'DNS server' in s_roles:
                    dns_servers.append(s_name)

            # 1. If all masters have weight == 0 then all are equally
            #    weighted.
            # 2. If any masters have weight == 0 then they have an
            #    extremely small chance of being chosen, percentage is
            #    0.1.
            # 3. Otherwise it's percentage change is based on the sum of
            #    the weights of non-zero masters.
            dividend = 100.0
            if weight_sum != 0:
                for server in servers_additional_info.values():
                    if int(server['ipaserviceweight'][0]) == 0:
                        dividend = dividend - 0.1
            for server in servers_additional_info.values():
                if weight_sum == 0:
                    val = 100 / len(servers)
                elif int(server['ipaserviceweight'][0]) == 0:
                    val = 0.1
                else:
                    val = (
                        int(server['ipaserviceweight'][0]) *
                        dividend / weight_sum
                    )
                server['service_relative_weight'] = [
                    u'{:.1f}%'.format(val)
                ]
            if servers_name:
                result['result']['servers_server'] = servers_name

            if dns_servers:
                result['result']['dns_server'] = dns_servers

            if not dns_servers and servers_additional_info:
                self.add_message(messages.LocationWithoutDNSServer(
                    location=keys[0]
                ))

        result['servers'] = servers_additional_info

        return result
