from django.db import models
from django.db.models import signals
from django.dispatch import dispatcher
from django.core.cache import cache as cache
from django.contrib.auth.models import User as DjangoUser
from django.contrib.auth.models import Group as DjangoGroup
from mail.utils import get_hash, EnabledManager, AddressManager


DOMAIN_TRANSPORT_CHOICES = (
    ('virtual:', 'Virtual Domain'),
    ('mailman:', 'Mailman-managed Domain'),
)

DEFAULT_QUOTA = 0;
DEFAULT_MAXMAILBOX = 0;

class Server(models.Model):
    """
    Stores info about our servers
    """
    name = models.CharField('Server FQDN',
            max_length=255,
            unique=True)
    info = models.TextField(null=True, blank=True)
    public_key = models.TextField(null=True, blank=True,
            help_text="Paste the public GPG key of the server here. This will "
            "enable this server to fetch GPG-encrypted and signed versions of "
            "all of his maps securly via HTTP in the future")
    key_id = models.CharField(max_length=40, blank=True,
            null=True, editable=False)
    smtp = models.BooleanField()
    xmpp = models.BooleanField()
    imap = models.BooleanField()
    imap_proxy_for = models.ForeignKey("Server", blank=True, null=True,
            help_text="The Dovecot IMAP Server has a proxy feature. If you set "
            "this host to another IMAP capable host, all users from the other "
            "host will be able to log into this IMAP Server. Their connections "
            "will get transparently proxied to the other server.")
    pop3 = models.BooleanField()

    objects = models.Manager()
    enabled_objects = models.Manager()

    def _public_key(self):
        if self.public_key is not None and \
                self.public_key.startswith(
                        '-----BEGIN PGP PUBLIC KEY BLOCK-----'):
            return str(self.public_key.strip())
        else:
            return None
    clean_public_key = property(_public_key)

    def _imap_accounts(self):
        return Account.enabled_objects.filter(
            config__in=self.imap_for_config.all())
    imap_accounts = property(_imap_accounts)
    def _imap_account_count(self):
        return self.imap_accounts.count()
    _imap_account_count.verbose_name = "Imap Accounts"
    imap_account_count = property(_imap_account_count)

    def _pop3_accounts(self):
        return Account.enabled_objects.filter(
            config__in=self.pop3_for_config.all())
    pop3_accounts = property(_pop3_accounts)
    def _pop3_account_count(self):
        return self.pop3_accounts.count()
    pop3_account_count = property(_pop3_account_count)

    def _smtp_accounts(self):
        return Account.enabled_objects.filter(
            config__in=self.smtp_for_config.all())
    smtp_accounts = property(_smtp_accounts)
    def _smtp_account_count(self):
        return self.smtp_accounts.count()
    smtp_account_count = property(_smtp_account_count)

    def _xmpp_accounts(self):
        return Account.enabled_objects.filter(xmpp=True).filter(
            config__in=self.xmpp_for_config.all())
    xmpp_accounts = property(_xmpp_accounts)
    def _xmpp_account_count(self):
        return self.xmpp_ccounts.count()
    xmpp_account_count = property(_xmpp_account_count)

    def _all_accounts(self):
        return self.smtp_accounts | self.pop3_accounts | self.imap_accounts | \
                self.xmpp_accounts
    all_accounts = property(_all_accounts)

    def responsible_for(self, obj):
        """
        Checks wether self is the responsible smtp-server for a given
        Account or Address
        """
        if isinstance(obj, Account):
            return obj.smtp and (self == obj.config.smtp_server)
        if isinstance(obj, Domain):
            return self == obj.config.smtp_server
        elif isinstance(obj, Address):
            return self.responsible_for(obj.domain)

    def __unicode__(self):
        return self.name

class Config(models.Model):
    """
    Defines a Scheme for UID, GID, Maildir of Domains
    """
    name = models.CharField('Config Name', max_length=200)
    smtp_server = models.ForeignKey(Server, related_name="smtp_for_config",
            blank=True, null=True,
            limit_choices_to={'smtp': True})
    xmpp_server = models.ForeignKey(Server, related_name="xmpp_for_config",
            blank=True, null=True,
            limit_choices_to={'xmpp': True})
    imap_server = models.ForeignKey(Server, related_name="imap_for_config",
            blank=True, null=True,
            limit_choices_to={'imap': True})
    pop3_server = models.ForeignKey(Server, related_name="pop3_for_config",
            blank=True, null=True,
            limit_choices_to={'pop3': True})
    info = models.TextField(null=True, blank=True)

    prefix = models.CharField('Maildir Prefix', max_length=300)
    uid = models.IntegerField('vMail UID')
    gid = models.IntegerField('vMail GID')

    admins = models.ForeignKey(DjangoGroup)

    def __unicode__(self):
        return self.name

class Domain(models.Model):
    """
    The FQDNs known to our System
    """
    name = models.CharField(max_length=255,
            unique=True,
            help_text="Fully Qualified Domain Name (FQDN)")
    max_mailboxes = models.IntegerField('Max Mailboxes',
            default=DEFAULT_MAXMAILBOX)
    max_quota = models.IntegerField('Max. Quota',
            default=DEFAULT_QUOTA)
    created_date = models.DateTimeField('Created At',
            editable=True, auto_now_add=True)
    enabled = models.BooleanField('Enabled', default=True)
    transport = models.CharField(max_length=128,
            choices=DOMAIN_TRANSPORT_CHOICES,
            default=DOMAIN_TRANSPORT_CHOICES[0],
            help_text="Use Virtual Domain for most domains",)
    admins = models.ManyToManyField(DjangoGroup,
            related_name="admin_for_domain")
    config = models.ForeignKey(Config, help_text="This must correspond to the "
            "Config the accounts belong to also")

    def _address_count(self):
        return self.address_set.count()
    address_count = property(_address_count)

    def __unicode__(self):
        return u"%s" % (self.name)

    def __repr__(self):
        return u"<domain %s:%s>" % (self.transport, self.name)

    objects = models.Manager()
    enabled_objects = EnabledManager()

    def _smtp_server(self):
        return self.config.smtp_server
    smtp_server = property(_smtp_server)

    def _xmpp_server(self):
        return self.config.xmpp_server
    xmpp_server = property(_xmpp_server)

    def _pop3_server(self):
        return self.config.pop3_server
    pop3_server = property(_pop3_server)

    def _imap_server(self):
        return self.config.imap_server
    imap_server = property(_imap_server)

    class Meta:
        ordering = ['name']

class Account(models.Model):
    """
    Every Mail-, XMPP-, POP3- or IMAP-User known to our System
    """
    name = models.CharField(unique=True, max_length=255)
    enabled = models.BooleanField('Enabled', default=True)
    password = models.CharField(max_length=255, blank=True,
            help_text="crypt()ed version of the password")
    password_clear = models.CharField('ClearText Password',
            max_length=200, blank=True)
    real_name = models.CharField(max_length=255, blank=True, null=True)
    alternative_mail = models.EmailField(blank=True, null=True)
    quota = models.IntegerField('Quota', editable=True, default=0)
    prefix = models.CharField('additional Maildir Prefix', max_length=300,
            blank=True, null=True)
    created_date = models.DateTimeField('Created At',
            auto_now_add=True, editable=True)

    admins = models.ManyToManyField(DjangoGroup,
            related_name="admin_for_account")

    config = models.ForeignKey(Config)

    smtp = models.BooleanField('SMTP',
               default=True,
           help_text="Allow incoming Email and SMTP Authentification")
    imap = models.BooleanField('IMAP', default=True,
            help_text="Allow IMAP4 access")
    pop3 = models.BooleanField('POP3', default=True,
            help_text="Allow POP3 access")
    xmpp = models.BooleanField('XMPP', default=False,
            help_text="""Allow XMPP/Jabber access. This allows
                 the user to change the password themselves""")
    primary_address = models.ForeignKey('Address', help_text="""needs to be set
            in order for xmpp to work""", blank=True, null=True,
            related_name="primary_accounts")

    def set_password(self, cleartext):
        if len(cleartext)>0:
            self.password = get_hash(cleartext) 

    def check_password(self, cleartext):
        return self.password == get_hash(cleartext, self.password)

    def _address_count(self):
        return self.addresses.count()
    address_count = property(_address_count)

    def _mailbox(self):
        if self.prefix == '' or self.prefix is None:
            return (u"%s/%s/" % (self.config.prefix, self.name)).lower()
        elif self.name.find("@") > -1:
            return (u"%s/%s/%s/" % (self.config.prefix,
                self.prefix,
                self.name.split("@")[0])).lower()
        else:
            return (u"%s/%s/%s/" % (self.config.prefix,
                self.prefix,
                self.name)).lower()
    mailbox = property(_mailbox)

    def _is_mailuser(self):
        return self.smtp or self.imap or self.pop3
    is_mailuser = property(_is_mailuser)

    def _smtp_server(self):
        return self.config.smtp_server
    smtp_server = property(_smtp_server)

    def _xmpp_server(self):
        return self.config.xmpp_server
    xmpp_server = property(_xmpp_server)

    def _pop3_server(self):
        return self.config.pop3_server
    pop3_server = property(_pop3_server)

    def _imap_server(self):
        return self.config.imap_server
    imap_server = property(_imap_server)

    def _clean_password(self):
        if self.password == '':
            return '!'
        else:
            return self.password
    clean_password = property(_clean_password)

    def save(self):
        self.set_password(self.password_clear)
        self.password_clear = ''
        super(Account, self).save();

    def __unicode__(self):
        return u"%s" % (self.name,)

    class Meta:
        ordering = ('enabled', 'xmpp')

    objects = models.Manager()
    enabled_objects = EnabledManager()

class Address(models.Model):
    """
    Email Forwardings in the System
    """
    address = models.CharField(max_length=255, blank=True, null=True)
    domain = models.ForeignKey(Domain)

    enabled = models.BooleanField('Enabled',default=True)
    recipients = models.TextField(max_length=16384, blank=True, null=True)

    # TODO:
    #  - restrict available accounts to only those accounts that are available
    #    for the specific chosen domain. 
    #    KEEP THIS IN MIND WHEN WRITING VIEWS!
    #  - Check wether Postfix allows an Address to have multiple
    #    Accounts. If not, we have to disallow that and use Recipients
    #    instead.

    account = models.ForeignKey(Account, related_name="addresses",
            blank=True, null=True,
            help_text='Please be aware that you may only choose an account '
            'that has the same Config, especially the same UID here as only '
            'the first one will get chosen by postfix to write it\'s email as.')

    def recipient_count(self):
        if not self.recipients:
            return 0
        # reformat comma-seperated recipient list
        line = "".join(self.recipients.splitlines())
        # extract all recipients
        list = [x.strip() for x in line.split(',') if x != '']
        return len(list)
    recipient_count.verbose_name_plural = "Recipients"

    def has_account(self):
        return isinstance(self.account, Account)
    has_account.boolean = True

    def _gid(self):
        if self.has_account() and self.account.is_mailuser:
            return self.account.config.gid
        else:
            return None
    gid = property(_gid)

    def _uid(self):
        if self.has_account() and self.account.is_mailuser:
            return self.account.config.uid
        else:
            return None
    uid = property(_uid)

    def __unicode__(self):
        return u"%s@%s" % (self.address, self.domain.name)

    class Meta:
        unique_together = (('address', 'domain'),)
        ordering = ('domain', 'address')
        verbose_name_plural = 'Addresses'

    objects = models.Manager()
    enabled_objects = AddressManager()

import maps
TIMEOUT = 60 * 60 * 24 # a day

def update(sender, instance, signal, *args, **kwargs):
    def red(x, y):
        return x+'\n'+y

    for server in Server.objects.all():
        all_maps = {}
        all_maps['virtual_alias_map'] = reduce(red,
                maps.postfix_map(maps.virtual_alias_map(server)), "")
        all_maps['virtual_uid_map'] = reduce(red,
                maps.postfix_map(maps.uid(server)), "")
        all_maps['virtual_gid_map'] = reduce(red,
                maps.postfix_map(maps.gid(server)), "")
        all_maps['virtual_mailbox_map'] = reduce(red,
                maps.postfix_map(maps.mailbox(server)), "")
        all_maps['virtual_mailbox_domains'] = reduce(red,
                maps.postfix_map(maps.domains(server)), "")
        all_maps['dovecot_userdb'] = reduce(red,
                maps.dovecot_map(maps.dovecot_userdb(server)), "")
        all_maps['dovecot_passdb'] = reduce(red,
                maps.dovecot_map(maps.dovecot_passdb(server)), "")

        for name, map in all_maps.iteritems():
            key = '%s/%s' % (server.name, name)
            if cache.get(key) != map:
                print 'map changed! ' + key
                cache.set(key, map, TIMEOUT)


#signals.post_save.connect(update, sender=Server)
#signals.post_save.connect(update, sender=Account)
#signals.post_save.connect(update, sender=Address)
#signals.post_save.connect(update, sender=Domain)
#signals.post_save.connect(update, sender=Config)


