# -*- coding: utf-8 -*-
from django.db import models
from django.core.exceptions import ValidationError
from netfields import InetAddressField, NetManager
import ldapdb.models
from ldapdb.models.fields import CharField, ListField

from coin.mixins import CoinLdapSyncMixin
from coin.offers.models import OfferSubscription
from coin.offers.backends import ValidateBackendType
from coin import utils
from coin import validation


class VPNSubscription(CoinLdapSyncMixin, models.Model):
    backend_name = "openvpn_ldap"
    administrative_subscription = models.OneToOneField(
        'offers.OfferSubscription',
        related_name=backend_name,
        validators=[ValidateBackendType(backend_name)])
    activated = models.BooleanField(default=False)
    login = models.CharField(max_length=50, unique=True)
    password = models.CharField(max_length=256)
    ipv4_endpoint = InetAddressField(validators=[validation.validate_v4],
                                     blank=True, null=True)
    ipv6_endpoint = InetAddressField(validators=[validation.validate_v6],
                                     blank=True, null=True)
    comment = models.CharField(blank=True, max_length=512)

    objects = NetManager()

    # These two methods are part of the general configuration interface.
    def save_subnet(self, subnet, creation):
        self.check_endpoints(delete=True)
        # We potentially changed the endpoints, so we need to save.
        self.full_clean()
        self.save()

    def delete_subnet(self, subnet):
        self.save_subnet(subnet, False)

    def get_subnets(self, version):
        subnets = self.administrative_subscription.ip_subnet.all()
        return [subnet for subnet in subnets if subnet.inet.version == version]

    def sync_to_ldap(self, creation):
        if creation:
            config = LdapVPNConfig()
        else:
            config = LdapVPNConfig.objects.get(pk=self.login)
        config.login = config.sn = self.login
        config.password = self.password
        config.active = 'yes' if self.activated else 'no'
        config.ipv4_endpoint = utils.str_or_none(self.ipv4_endpoint)
        config.ipv6_endpoint = utils.str_or_none(self.ipv6_endpoint)
        config.ranges_v4 = [str(s) for s in self.get_subnets(4)]
        config.ranges_v6 = [str(s) for s in self.get_subnets(6)]
        config.save()

    def delete_from_ldap(self):
        LdapVPNConfig.objects.get(pk=self.login).delete()

    def generate_endpoints(self, v4=True, v6=True):
        """Generate IP endpoints in one of the attributed IP subnets.  If there is
        no available subnet for a given address family, then no endpoint
        is generated for this address family.  If there already is an
        endpoint, do nothing.

        Returns True if an endpoint was generated.

        TODO: this should be factored for other technologies (DSL, etc)

        """
        subnets = self.administrative_subscription.ip_subnet.all()
        updated = False
        if v4 and self.ipv4_endpoint is None:
            subnets_v4 = [s for s in subnets if s.inet.version == 4]
            if len(subnets_v4) > 0:
                self.ipv4_endpoint = subnets_v4[0].inet.ip
                updated = True
        if v6 and self.ipv6_endpoint is None:
            subnets_v6 = [s for s in subnets if s.inet.version == 6]
            if len(subnets_v6) > 0:
                # With v6, we choose the second host of the subnet (cafe::1)
                gen = subnets_v6[0].inet.iter_hosts()
                gen.next()
                self.ipv6_endpoint = gen.next()
                updated = True
        return updated

    def check_endpoints(self, delete=False):
        """Check that the IP endpoints are included in one of the attributed IP
        subnets.

        If [delete] is True, then simply delete the faulty endpoints
        instead of raising an exception.
        """
        subnets = self.administrative_subscription.ip_subnet.all()
        is_faulty = lambda endpoint : endpoint and not any([endpoint in subnet.inet for subnet in subnets])
        if is_faulty(self.ipv4_endpoint):
            if delete:
                self.ipv4_endpoint = None
            else:
                raise ValidationError("Endpoint {} is not in an attributed range".format(self.ipv4_endpoint))
        if is_faulty(self.ipv6_endpoint):
            if delete:
                self.ipv6_endpoint = None
            else:
                raise ValidationError("Endpoint {} is not in an attributed range".format(self.ipv6_endpoint))

    def clean(self):
        # Hash password if needed
        self.password = utils.ldap_hash(self.password)
        # If saving for the first time and IP endpoints are not specified,
        # generate them automatically.
        if self.pk is None:
            self.generate_endpoints()
        self.check_endpoints()

    def __unicode__(self):
        return 'VPN ' + self.login


class LdapVPNConfig(ldapdb.models.Model):
    # TODO: déplacer ligne suivante dans settings.py
    base_dn = "ou=vpn,o=ILLYSE,l=Villeurbanne,st=RHA,c=FR"
    object_classes = ['person', 'organizationalPerson', 'inetOrgPerson',
                      'top', 'radiusprofile']

    login = CharField(db_column='cn', primary_key=True, max_length=255)
    sn = CharField(db_column='sn', max_length=255)
    password = CharField(db_column='userPassword', max_length=255)
    active = CharField(db_column='dialupAccess', max_length=3)
    ipv4_endpoint = CharField(db_column='radiusFramedIPAddress', max_length=16)
    ipv6_endpoint = CharField(db_column='postalAddress', max_length=40)
    ranges_v4 = ListField(db_column='radiusFramedRoute')
    ranges_v6 = ListField(db_column='registeredAddress')

    def __unicode__(self):
        return self.login

    class Meta:
        managed = False  # Indique à South de ne pas gérer le model LdapUser
