# (c) cavaliba.com - IAM - api.py

import re


from django.utils import timezone
from django.db.models import F

import app_home.cache as cache

from app_user.ip import get_user_ip
from app_user.role import role_get_by_name
from app_user.role import role_get_permissions

from app_home.models import CavalibaAPIStat
from app_data.data import Instance 
from app_data import crypto

# ==============================================================================
# api helper
# ==============================================================================

def api_update_error(keyname=None):
    
    if not keyname:
        return
    
    CavalibaAPIStat.objects.get_or_create(keyname=keyname)
    CavalibaAPIStat.objects.filter(keyname=keyname)\
        .update(error_count=F("error_count") + 1, last_error = timezone.now())


def api_update_success(keyname=None):

    if not keyname:
        return
    
    CavalibaAPIStat.objects.get_or_create(keyname=keyname)
    CavalibaAPIStat.objects.filter(keyname=keyname)\
        .update(success_count=F("success_count") + 1, last_success = timezone.now())
    

def check_ip_filter(ip, filter):
    # TODO : ip in ACL filter ? 
    # filter format [!]range range range"  or "*"" or "0.0.0.0/0"
    return True


# ==============================================================================
# start_api
# ==============================================================================

# aaa = {}
# aaa["is_allowed"] = False
# aaa["keyname"] = None
# aaa["username"] = API:KEYNAME
# aaa["is_readonly"] = True
# aaa["error"] = "no action performed"
# aaa["user_ip"] = None
# aaa["acl"] = None                       # text block of all acl entries
# aaa["perms"] = []                 # list of perm keynames ["p_xxx", "..."]
# aaa["aaa"] = {'username':keyname, 'user_ip': user_ip }


def start_api(request, permission=None):

    # check API key 
    # check time range, IP range, enabled, ...
    # build API permissions/ACL structure
    # check provided permission if not None

    aaa = {}
    aaa["keyname"] = None
    aaa["is_readonly"] = True
    aaa["error"] = "no action performed"
    aaa["acl"] = None

    aaa["is_trusted_ip"] = False
    aaa["is_anonymous"] = True
    aaa["is_authenticated"] = False
    aaa["is_visitor"] = False
    aaa["is_admin"] = False
    aaa["is_allowed"] = False
    aaa["user"] = None
    aaa["userid"] = None
    aaa["username"] = "API:?"
    aaa["user_ip"] = get_user_ip(request)
   
    aaa["perms"] = []

    #flush_cache_per_request()
    cache.init()

    #header = request.META.get('HTTP_AUTHORIZATION')
    cavaliba_auth = request.headers.get('X-Cavaliba-Key','')
    if not cavaliba_auth:
        aaa["error"] = "missing key"
        return aaa

    (keyname, clear_secret) = cavaliba_auth.split( )
    # keyname 
    m = re.compile(r'[a-zA-Z0-9()_/.-]*$')

    if not keyname:
        aaa["error"] = "invalid key"
        return aaa
    if not m.match(keyname):
        aaa["error"] = "invalid key"
        return aaa

    aaa["keyname"] = keyname

    instance = Instance.from_keyname(classname="_apikey", keyname=keyname)
    if not instance:
        aaa["error"] = "invalid keyname"
        return aaa
    
    # is_enabled
    if not instance.is_enabled:
        aaa["error"] = "invalid keyname"
        api_update_error(keyname)
        return aaa

    # is_readonly
    instance.fields["is_readonly"].get_first_value()

    # secret
    if not crypto.hash_check(clear_secret, instance.fields["secret"].get_first_value()):
        aaa["error"] = "invalid keyname/secret"
        api_update_error(keyname)
        return aaa

    aaa["is_authenticated"] = True


    # is_readonly
    aaa["is_readonly"] = instance.fields["is_readonly"].get_first_value()

    # check not after
    # TODO

    # check timerange 
    # TODO
    
    # check ip source
    user_ip = get_user_ip(request)
    aaa["user_ip"] = user_ip
    ip_filter = instance.fields["ip_filter"].get_first_value()

    if len(ip_filter) > 0:
        # if not ip_filter:
        #     aaa["error"] = "invalid ip for key"
        #     api_update_error(keyname)
        #     return aaa

        r = check_ip_filter(user_ip, ip_filter)
        if not r:
            aaa["error"] = "invalid ip for key"
            api_update_error(keyname)
            return aaa

    # get ACL entries (multi-line Text Field)
    try:
        aaa["acl"] = instance.fields["acl"].get_first_value()
    except:
        aaa["acl"] = ""

    # TODO: CACHE
    aaa["perms"] = []
    perms = []

    # extract individual permissions / negate with !
    #[!]permission
    #[!]user:xxxxx
    #[!]role:xxxxx
    try:
        acl_entries = instance.fields["acl"].get_first_value().splitlines()
    except:
        # invalid acl from DB
        acl_entries = []

    for acl in acl_entries:
        if acl.startswith('!'):
            negate = True
            acl2 = acl[1:]
        else:
            negate = False
            acl2 = acl
        
        # user: role:
        if ':' in acl2:
            # TODO: user

            # role
            if acl2.startswith('role:'):
                rolename = acl2[5:]
                roleperms = role_get_permissions([rolename])
                for p in roleperms:
                    if negate:
                        if p in perms:
                            perms.remove(p)
                    else:
                        if p not in perms:
                            perms.append(p)
        
        # atomic permission
        else:
            if len(acl2) > 0:
                if negate:
                    if acl2 in perms:
                        perms.remove(acl2)
                else:
                    if acl2 not in perms:
                        perms.append(acl2)

    if 'roles' in instance.fields:
        for rolename in instance.fields['roles'].value:
            role_obj = role_get_by_name(rolename, enabled_only=True)
            if role_obj:
                for perm in role_obj.permissions.all():
                    perms.append(perm.keyname)

    # unique
    aaa["perms"] = list(set(perms))
   
    # if provided permission, check now
    if permission:
        if permission not in aaa["perms"]:
            aaa["error"] = "no permission"
            api_update_error(keyname)
            return aaa


    # OK / update logs
    api_update_success(keyname)    
    aaa["is_allowed"] = True
    aaa["error"] = ""
    aaa["username"] = 'API:' + aaa["keyname"]
    return aaa




