import xml.etree.ElementTree as ET
try:
    from django.conf import settings
except ImportError:
    settings = None
import datetime
import subprocess
import syslog

import htmlentitydefs
import re

pattern = re.compile("&(\w+?);")

def html_entity_decode_char(m, defs=htmlentitydefs.entitydefs):
    try:
        return defs[m.group(1)]
    except KeyError:
        return m.group(0)

def html_entity_decode(string):
    return pattern.sub(html_entity_decode_char, string)


def configxml():
    if settings:
        f = open(settings.PF_CONFIG_XML,'r')
    else:
        f = open('/conf/config.xml','r')
    root = ET.fromstring(f.read())
    f.close()
    return root

def get_all_pfusers():
    xml_users = configxml().findall('system/user')
    if xml_users is None:
        return {}
    users = {}

    for xml_user in xml_users:
        scope = xml_user.find('scope')
        if scope is None or scope.text != 'user':
            continue

        user = dict([(tag, xml_user.findtext(tag))
            for tag in ('uid', 'name', 'expires', 'descr')])

        user['descr'] = html_entity_decode(user['descr']).decode('iso-8859-1')
        f = open('/tmp/user-%s' % user['name'],'w')
        f.write('%s' % user)
        f.close()

        user['priv'] = set([priv.text for priv in xml_user.findall('priv')])

        expires = user.get('expires')
        if expires:
            try:
                user['expires'] = datetime.datetime.strptime(expires, '%m/%d/%Y').date() # pfSense format is mm/dd/YYYY
                user['ttl'] = (user['expires'] - datetime.date.today()).days
                if user['ttl'] < 0:
                    user['ttl'] = 0
            except:
                # pfSense xml error ? ok, I suppose the account is expired
                user['expires'] = datetime.date.today()
                user['ttl'] = 0
        else:
            user['expires'] = None
            user['ttl'] = -1 # no expiration

        user['disabled'] = xml_user.find('disabled') is not None

        users[user['uid']] = user

    # priv from groups
    xml_groups = configxml().findall('system/group')
    if not xml_groups:
        return users
    for xml_group in xml_groups:
        xml_members = xml_group.findall('member')
        if not xml_members:
            continue
        xml_privs = xml_group.findall('priv')
        if not xml_privs:
            continue
        privs = set([priv.text for priv in xml_privs])
        for uid in [xml_member.text for xml_member in xml_members]:
            user = users.get(uid)
            if user:
                user['priv'].update(privs)

    # 1) keep only users with the "univnautes-idp" privilege
    # 1bis) and without "univnautes-idp-admin" privilege
    # 2) new index of the dict is the username (instead of the uid)
    users = dict([(users[uid]['name'], users[uid]) for uid in users
        if ('univnautes-idp' in users[uid]['priv']) and ('univnautes-idp-admin' not in users[uid]['priv'])])
    return users

def call(action, **kwargs):
    # TODO: add some checks on kwargs...
    cmd = ['/usr/local/univnautes/bin/pf_useradm', ]
    cmd.append(action)
    cmd += [ '%s=%s' % (k,v.encode('iso-8859-1')) for k,v in kwargs.items() ]
    try:
        p = subprocess.Popen(cmd, close_fds=True,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE)
    except OSError, e:
        syslog.openlog("idpusersadmin/pf_useradmin", syslog.LOG_PID)
        syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO,
                "ERROR %s: OSError %s" % (action, e))
        return False, "ERROR: OSError %s" % e
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        syslog.openlog("idpusersadmin/pf_useradmin", syslog.LOG_PID)
        syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO,
                "ERROR %s: code %s (out:%s) (err:%s)" % (action, p.returncode, stdout, stderr))
        return False, stdout + stderr
    syslog.openlog("idpusersadmin/pf_useradmin", syslog.LOG_PID)
    syslog.syslog(syslog.LOG_LOCAL4 | syslog.LOG_INFO,
            "SUCCESS %s: %s" % (action, stdout))
    return True, stdout

def create(username, password, expires, disabled=False, descr='', multiple=False):
    if isinstance(expires, datetime.date):
        expires_str = expires.strftime('%m/%d/%Y')
    else:
        expires_str = ''
    if disabled:
        disabled_str='yes'
    else:
        disabled_str='no'
    privs = 'univnautes-idp'
    if multiple:
        privs += ',univnautes-idp-multiple'
    return call('create', name=username, password=password,
            expires=expires_str,
            disabled=disabled_str,
            privs=privs,
            descr=descr)

def desactivate(username):
    return call('update', name=username, disabled='yes')

def activate(username):
    return call('update', name=username, disabled='no')

def delete(username):
    return call('delete', name=username)

def update(username, password, expires, disabled, descr, multiple):
    if isinstance(expires, datetime.date):
        expires_str = expires.strftime('%m/%d/%Y')
    else:
        expires_str = ''
    if disabled:
        disabled_str='yes'
    else:
        disabled_str='no'
    privs = 'univnautes-idp'
    if multiple:
        privs += ',univnautes-idp-multiple'
    if password:
        return call('update', name=username, password=password,
                expires=expires_str,
                disabled=disabled_str,
                privs=privs,
                descr=descr)
    else:
        return call('update', name=username,
                expires=expires_str,
                disabled=disabled_str,
                privs=privs,
                descr=descr)

