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

import re

import csv
import json
import yaml

from django.http import HttpResponse
from django.forms.models import model_to_dict
from django.utils.translation import gettext as _
from django.utils import timezone
from django.db.models import Q

from app_home.configuration import get_configuration
from app_home.log import log, DEBUG, INFO, WARNING, ERROR, CRITICAL
import app_home.cache as cache

from .models import SireneUser
from .models import SireneGroup
from .forms import UserForm


from app_data.permissions import has_user_read_permission
from app_data.permissions import has_user_create_permission
from app_data.permissions import has_user_update_permission
from app_data.permissions import has_user_delete_permission



def user_get_form_blank():

    initial = {}
    return UserForm(initial=initial)


def user_get_form(user):

    user_attributs =  [
        "login", "firstname","lastname","displayname","email","mobile",
        "external_id", "is_enabled","description",
        "want_notifications", "want_24", "want_email", "want_sms", 
        "secondary_email", "secondary_mobile"
    ] 


    initial = {}
    for attrib in user_attributs:
        try:
            initial[attrib] = getattr(user,attrib, "")
        except:
            pass

    # groups (helper field, not in model)
    groups = SireneGroup.objects.filter(is_enabled=True, users__in=[user])
    if groups:
        initial["groups"] = [i for i in groups]

    return UserForm(initial=initial) 




def user_get_by_id(userid):
    #prefetch_related('roles__permissions')
    if not userid:
        return
    cachekey = f"_user::id::{userid}"
    dbuser = cache.cache2_iam_user.get(cachekey)
    if dbuser:
        return dbuser
    
    dbuser = SireneUser.objects.filter(pk=userid).first()
    if dbuser:
        cache.cache2_iam_user.set(cachekey, dbuser)
        cachekey2 = cachekey = f"_user::{dbuser.login}"
        cache.cache2_iam_user.set(cachekey2, dbuser)
    return dbuser


def user_get_by_login(login):
    # .prefetch_related('roles__permissions')
   
    if not login:
        return
    
    cachekey = f"_user::{login}"
    dbuser = cache.cache2_iam_user.get(cachekey)
    if dbuser:
        return dbuser
    
    dbuser = SireneUser.objects.filter(login=login).first()
    if dbuser:
        cache.cache2_iam_user.set(cachekey, dbuser)
    return dbuser

    


def user_get_by_data(data):   
    login = data.get('login', None)
    return  user_get_by_login(login)


def user_get_email(user=None):

    if not user:
        return

    if user.secondary_email:
        if len(user.secondary_email) > 0:
            return user.secondary_email
    return user.email


def user_get_mobile(user):
    if not user:
        return

    if user.secondary_mobile:
        if len(user.secondary_mobile) > 0:
            return user.secondary_mobile
    return user.mobile



def user_get_login_by_email(email=None):

    if not email:
        return
    if not '@' in email:
        return
    
    a = SireneUser.objects.filter(email=email).first()
    try:
        return a.login
    except:
        return


def user_search(query=None, page=1, size=10):
    
    m = re.compile(r'[a-zA-Z0-9()_/.-]*$')
    if not m.match(query):
        return []
    
    offset = (page - 1) * size
    limit = offset + size


    if query:
        return SireneUser.objects\
            .filter( Q(displayname__icontains=query) | Q(login__icontains=query) | Q(mobile__icontains=query) | Q(email__icontains=query) | Q(external_id__icontains=query) )\
            .order_by('login')[offset:limit]


    return  SireneUser.objects.order_by('login')[offset:limit]   
  

# ---

def user_init(data):

    login = data.get('login', None)
    if not login:
        return None, "user_init: no login"

    # skip if exists
    entry = user_get_by_login(login)
    if entry:
        return entry, None
        
    entry = SireneUser()
    return user_update(entry, data)



def user_create(data):

    login = data.get('login', None)
    if not login:
        return None, "user_create: no login"

    # create or update
    entry1 = user_get_by_login(login)
    if not entry1:
        entry1 = SireneUser()

    return user_update(entry1, data)
    

def user_update(user, data):
    ''' update user Object with data dict info'''

    user_attributs =  [
        "login", "firstname","lastname","displayname","email","mobile",
        "external_id", "is_enabled","description",
        "want_notifications", "want_24", "want_email", "want_sms", 
        "secondary_email", "secondary_mobile"
    ] 

    for attrib in user_attributs:
        if attrib in data:
            try:
                setattr(user, attrib, data[attrib])
            except:
                pass

    # save here to create object if new
    user.save()
    
    # groups helper field (not in model)
    if "groups" in data:

        # step 1: remove user from its previous groups
        user.sirenegroup_set.clear()

        # Step 2: add groups
        for gname in data["groups"]:
            gobj = SireneGroup.objects.filter(keyname=gname, is_role=False).first()
            if gobj:
                gobj.users.add(user)
                gobj.save()



    user.last_update = timezone.now()
    user.save()
    cache.cache2_iam_user.clear()

    return user, None



def user_update_by_data(data):
    user = user_get_by_data(data)
    if user:
        return user_update(user,data)


# ---
def user_switch(user, is_enabled):
    if user:
        if is_enabled:
            user.is_enabled = True
        else:
            user.is_enabled = False
        user.last_update = timezone.now()
        user.save()
        cache.cache2_iam_user.clear()
        return user, None
    else:
        return None, "user_switch: missing user"

def user_enable_by_id(userid):
    user = user_get_by_id(userid)
    return user_switch(user, True)

def user_enable_by_data(data):
    user = user_get_by_data(data)
    return user_switch(user, True)


def user_disable_by_id(userid):
    user = user_get_by_id(userid)
    return user_switch(user, False)

def user_disable_by_data(data):
    user = user_get_by_data(data)
    return user_switch(user, False)

# ---

def user_delete(user):
    if user:
        if user.login == "admin":
            # built-in, can't delete ; disable instead
            return None, "user_delete: can't delete admin"
        else:
            user.delete()
            cache.cache2_iam_user.clear()
            return user, None
    else:
        return None, "user_delete: missing user"

def user_delete_by_id(userid):
    user = user_get_by_id(userid)
    return user_delete(user)


def user_delete_by_data(data):
    user = user_get_by_data(data)
    return user_delete(user)



# -------------------------------------------------------
# LOADER / IMPORT
# -------------------------------------------------------
def load_user(datadict=None, verbose=None, aaa=None):

    if not datadict:
        return "load_user: missing data"

    if not aaa:
        log(WARNING, aaa=aaa, app="iam", view="user", action="load", status="DENY", data=_("Not allowed")) 
        return "load_user: missing aaa"


    keyname = datadict.get("keyname", None)
    # backward compatibility : login field accepted as keyname
    if not keyname:
        keyname = datadict.get("login", None)
    if not keyname: 
        return "load_user: missing keyname/login"
    
    if "login" not in datadict:
        datadict["login"] = keyname

    action = datadict.get("_action", "create")

    err = None

    if action == "init":
        if not has_user_create_permission(aaa=aaa):
            log(WARNING, aaa=aaa, app="iam", view="user", action="init", status="DENY", data=_("Not allowed")) 
            return "load_user: not allowed"
        u, err = user_init(datadict)

    elif action == "create":
        if not has_user_create_permission(aaa=aaa):
            log(WARNING, aaa=aaa, app="iam", view="user", action="create", status="DENY", data=_("Not allowed")) 
            return "load_user: not allowed"
        u, err = user_create(datadict)

    elif action == "update":
        if not has_user_update_permission(aaa=aaa):
            log(WARNING, aaa=aaa, app="iam", view="user", action="update", status="DENY", data=_("Not allowed")) 
            return "load_user: not allowed"
        u, err = user_update_by_data(datadict)

    elif action == "delete":
        if not has_user_delete_permission(aaa=aaa):
            log(WARNING, aaa=aaa, app="iam", view="user", action="delete", status="DENY", data=_("Not allowed")) 
            return "load_user: not allowed"
        u, err = user_delete_by_data(datadict)

    elif action == "enable":
        if not has_user_update_permission(aaa=aaa):
            log(WARNING, aaa=aaa, app="iam", view="user", action="update", status="DENY", data=_("Not allowed")) 
            return "load_user: not allowed"
        u, err = user_enable_by_data(datadict)

    elif action == "disable":
        if not has_user_update_permission(aaa=aaa):
            log(WARNING, aaa=aaa, app="iam", view="user", action="update", status="DENY", data=_("Not allowed")) 
            return "load_user: not allowed"
        u, err = user_disable_by_data(datadict)
    else:
        return f"load_user: unknown action {action}"

    if err:
        return f"load_user: {err}"
    return




# -------------------------------------------------------
# EXPORTs
# -------------------------------------------------------


def user_listdict_format(users):
    ''' users Models to list of dict [ {}, {}, {}, ... ]'''

    dict_attributs =  [
        "login", "firstname","lastname", "displayname", "email","mobile", 
        "external_id", "is_enabled", "description",
        "want_notifications", "want_24", "want_email", "want_sms", 
        "secondary_email", "secondary_mobile"
    ]

    mylist = []
    for user in users:

        if not isinstance(user, SireneUser):
            continue

        m = {}
        m["classname"] = "_user"
        m.update(model_to_dict(user, fields=dict_attributs))
        
        #m["_action"] = "create"

        # remove null values
        m2= {}
        for k,v in m.items():
            if not isinstance(v,bool):
                if v:
                    m2[k] = v
            else:
                m2[k] = v


        mylist.append(m2)

    return mylist

# JSON
def user_json_response(users):
    datalist = user_listdict_format(users)
    filedata = json.dumps(datalist, indent=4, ensure_ascii = False)
    response = HttpResponse(filedata, content_type='text/json')  
    response['Content-Disposition'] = 'attachment; filename="users.json"'
    return response

# YAML
class MyYamlDumper(yaml.SafeDumper):
    def write_line_break(self, data=None):
        super().write_line_break(data)
        if len(self.indents) < 2:
            super().write_line_break()

def user_yaml_response(users):
    datalist = user_listdict_format(users)
    filedata = yaml.dump(datalist, allow_unicode=True, Dumper=MyYamlDumper, sort_keys=False)
    response = HttpResponse(filedata, content_type='text/yaml')  
    response['Content-Disposition'] = 'attachment; filename="users.yaml"'
    return response


# CSV
def user_csv_response(users):

    # headers
    csv_attributs =  [
        "classname", "login", "firstname","lastname", "displayname", "email","mobile", 
        "external_id", "is_enabled","description",
        "want_notifications", "want_24", "want_email", "want_sms", 
        "secondary_email", "secondary_mobile"
    ] 

    special_attributs = ["classname", "site", "roles"]

    delimiter = get_configuration(keyname="CSV_DELIMITER")


    response = HttpResponse(content_type='text/csv')  
    response['Content-Disposition'] = 'attachment; filename="users.csv"'
    writer = csv.writer(response, delimiter=delimiter)

    # headers = []
    # for attrib in csv_attributs:
    #     headers.append(attrib)

    writer.writerow(csv_attributs)

    for item in users:
        mye = ["_user"]

        # standard attributs
        for attrib in csv_attributs:
            if attrib in special_attributs:
                continue
            myvalue = getattr(item, attrib, "")
            mye.append(myvalue)

        writer.writerow(mye)

    return response
