# app_user - views.py
# (c) cavaliba.com 

import os
from datetime import datetime, timedelta
import time
import base64
import random
import csv
import jwt
import json
import re

import pprint

from django.utils import timezone
from django.conf import settings
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.contrib import messages
from django.utils.translation import gettext as _
from django.db.models import Q
from django.contrib.auth import logout as django_logout
from django.utils.translation import get_language

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

from .models import SireneUser
from .models import SireneGroup

from .aaa import start_view
from .aaa import get_aaa

from .aaa import get_groups_for_user

from .user import user_get_by_login
from .user import user_csv_response
from .user import user_json_response
from .user import user_yaml_response
from .user import user_search
# from .user import import_json
# from .user import import_yaml
from .user import user_get_by_id
from .user import user_delete_by_id
from .user import user_update
from .user import user_create   # or update
from .user import user_get_form_blank 
from .user import user_get_form

from .user import user_get_mobile
from .user import user_get_email

from .forms import UserForm
from .forms import UserPrefForm
from .forms import UserUploadForm

from app_sirene.sms import sms_check_valid_number
from app_sirene.sms import get_sms_quota

# Celery tasks
from app_sirene.mail import task_send_mail
from app_sirene.sms import task_send_sms

from app_data.permissions import has_iam_export_permission
from app_data.revision import revision_add_raw
from app_data.revision import revision_get

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


# -----------------------------------------
# private
#-----------------------------------------

def private(request):

    context = start_view(request, app="iam", view="private", noauth="app_home:private", 
        perm="p_iam_access", noauthz="app_home:index")
    if context["redirect"]:
        return redirect(context["redirect"])

    return redirect("app_user:user_list")


# ----------------------------------------------------------
# USER list
# ----------------------------------------------------------    
def list(request):
    '''  display user list - ?o=csv to export as CSV (yaml, json)  '''

    context = start_view(request, app="iam", view="list", noauth="app_home:private", 
        perm="p_user_read", noauthz="app_home:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]

    # p_user_read is enough  in start_view : no per-user permission.

    # bigset or not
    bigset_size = int(get_configuration("data","DATA_BIGSET_SIZE"))
    count = SireneUser.objects.count()
    bigset = True
    context["bigset"] = bigset
    context["count"] = count
    
    # Filter POST (form)
    #query = request.GET.get("q", "")
    query = ""
    if request.method == "POST":
        if request.POST.get('query'):
            query = request.POST.get('query')
            m = re.compile(r'[a-zA-Z0-9()_/.-]*$')
            if not m.match(query):
                #return redirect("app_user:user_list")
                query = ""
    context["query"] = query


    # # partial query + paginate if bigset
    # if bigset:
    try:
        size = int(request.GET.get("size", bigset_size))
        page = int(request.GET.get("page",1))
    except Exception as e:
        print(e)
        return redirect("app_user:user_list")

    if page < 1 or size > 10000 or size < 1:
        return redirect("app_user:user_list")


    users = user_search(query=query, page=page, size=size)

    # export requested (o=xxxx)
    output = request.GET.get("o","")
    if output != "":

        # permission
        if not has_iam_export_permission(aaa=aaa):
            log(WARNING, aaa=aaa, app="iam", view="list", action="export", status="DENY", data=_("Not allowed")) 
            messages.add_message(request, messages.ERROR, _("Not allowed"))
            return redirect("app_sirene:private")

        # NEXT : partial export (selection only)

        # size max for interactive export ?
        export_max = int(get_configuration("data","EXPORT_INTERACTIVE_MAX_SIZE"))

        if count > export_max:
            log(ERROR, aaa=aaa, app="iam", view="list", action="export", status="KO", data="Export too big") 
            messages.add_message(request, messages.ERROR, _("Export too large for interactive export."))
            return redirect("app_user:user_list")

        if output == "csv":
            log(DEBUG, aaa=aaa, app="iam", view="user_list", action="export_csv", status="OK", data="") 
            response = user_csv_response(users)
            return response   

        elif output == "json":
            log(DEBUG, aaa=aaa, app="iam", view="user_list", action="export_json", status="OK", data="") 
            response = user_json_response(users)
            return response   

        elif output == "yaml":
            log(DEBUG, aaa=aaa, app="iam", view="user_list", action="export_yaml", status="OK", data="") 
            response = user_yaml_response(users)
            return response   

    
    # PREV | FIRST || CURRENT or ... || LAST | NEXT
    context["size"] = size
    context["page"] = page
    page_last = int (count / size) + 1
    # if count * size <= page_last:
    #     page_last = page_last + 1

    if page > 1:
        context["page_prev"] = page - 1
    else:
        context["page_prev"] = page
    
    if page == 1:
        context["page_first"] = True
    else:
        context["page_first"] = False

    if page > 1 and page < page_last:
        context["page_current"] = True
    else:
        context["page_current"] = False

    context["page_last"] = page_last
    if page < page_last:
        context["page_last_active"] = False
    else:
        context["page_last_active"] = True

    if page < page_last:
        context["page_next"] = page + 1
    else:
        context["page_next"] = page

    # else:
    #     users = SireneUser.objects.all().order_by('login')
    #     # users = SireneUser.objects.all().exclude(login='admin').prefetch_related('roles').order_by('login')   
    
    upload_form = UserUploadForm()
    
    log(DEBUG, aaa=aaa, app="iam", view="list", action="get", status="OK", data=f"{count} users")


    context["users"] = users
    context["upload_form"] = upload_form
    return render(request, 'app_user/user_list.html', context)


# ----------------------------------------------------------
# User delete
# ----------------------------------------------------------    
    
def delete(request, userid):
    ''' delete user  '''

    context = start_view(request, app="iam", view="user_delete", noauth="app_home:index", 
        perm="p_user_delete", noauthz="app_sirene:index")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]

    if request.method == "POST":

        user, err = user_delete_by_id(userid=userid) 

        if err:
            messages.add_message(request, messages.ERROR, _("Failed to delete user"))
            log(ERROR, aaa=aaa, app="iam", view="user_delete", action="delete", status="FAIL", data=err) 
        else:
            revision_add_raw(aaa=aaa, classname="_user", keyname=user.login, action="delete")
            messages.add_message(request, messages.SUCCESS, _("User deleted"))
            log(INFO, aaa=aaa, app="iam", view="user_delete", action="delete", status="OK", data=f"{user.login}") 

    return redirect("app_user:user_list")


# ----------------------------------------------------------
# User detail
# ----------------------------------------------------------    

def detail(request, userid=None):
    ''' display role detail '''

    context = start_view(request, app="iam", view="user_detail", 
        noauth="app_user:private", perm="p_user_read", noauthz="app_user:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]

    user = user_get_by_id(userid)
    if not user:
        messages.add_message(request, messages.ERROR, _("Not allowed"))
        log(WARNING, aaa=aaa, app="iam", view="user", action="detail", status="KO", data=_("Not allowed") )
        return redirect("app_user:user_list")

    log(DEBUG, aaa=aaa, app="iam", view="user", action="detail", status="OK", data=user.login )

    # get group objects
    direct, computed, indirect = get_groups_for_user(user, mode="obj")
    context["direct"] = direct
    context["computed"] = computed
    context["indirect"] = indirect

    permissions = []
    for g in direct:
        for p in g.permissions.all():
            if p not in permissions:
                permissions.append(p)
    for g in indirect:
        for p in g.permissions.all():
            if p not in permissions:
                permissions.append(p)
    context["permissions"] = permissions

    # revisions - V3.23
    context['revisions'] = revision_get(classname="_user", keyname=user.login)


    # can use YAML Editor ?
    if 'p_data_import' in aaa['perms']:
        context['ui_texteditor'] = True
        
    context["user"] = user
    return render(request, 'app_user/user_detail.html', context)



# ----------------------------------------------------------
# User edit
# ----------------------------------------------------------    

def edit(request, userid=None):
    ''' user  edit form'''

    context = start_view(request, app="iam", view="user.edit", noauth="app_home:index", 
        perm="p_user_update", noauthz="app_user:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]


    user = None

    # if user is provided, it's an update
    if userid:
        user = user_get_by_id(userid)
        if not user:
            messages.add_message(request, messages.ERROR, _("Not allowed"))
            log(WARNING, aaa=aaa, app="iam", view="user", action="edit", status="KO", data=_("Not allowed"))
            return redirect("app_user:user_list")

    # else, it's a create : check p_user_create
    else:
        if "p_user_create" not in aaa["perms"]:
            messages.add_message(request, messages.ERROR, _("Not allowed"))
            log(WARNING, aaa=aaa, app="iam", view="user", action="create", status="KO", data=_("Not allowed"))
            return redirect("app_user:user_list")


    if request.method == "POST":

        form = UserForm(request.POST)
        
        if form.is_valid():

            action = "create"
            # update
            if user:
                user, err = user_update(user, form.cleaned_data)
                action="edit"
            # create                
            else:
                user, err = user_create(data=form.cleaned_data)
            
            # success ?
            if user:
                revision_add_raw(aaa=aaa, classname="_user", keyname=user.login, action=action)
                messages.add_message(request, messages.SUCCESS, _("User saved : ") + user.login )
                log(INFO, aaa=aaa, app="iam", view="edit", action="save", status="OK", data=f"{user.login}")
            else:
                messages.add_message(request, messages.ERROR, _("Failed to save user"))
                log(WARNING, aaa=aaa, app="iam", view="edit", action="save", status="KO", data="")
            
            return redirect("app_user:user_detail", user.id)

        else:
            messages.add_message(request, messages.ERROR, _("Invalid User form"))
            log(DEBUG, aaa=aaa, app="iam", view="edit", action="save", status="KO", data="Invalid User form")

    else:

        if user:
            form = user_get_form(user)
            log(DEBUG, aaa=aaa, app="iam", view="edit", action="edit", status="OK", data=f"{user.login}")

        else: 
            form = user_get_form_blank()
            log(DEBUG, aaa=aaa, app="iam", view="edit", action="new", status="OK", data="")

    context["form"] = form
    if user:
        context["user"] = user

    return render(request, 'app_user/user_edit.html', context)


# # ----------------------------------------------------------
# # User Import
# # ----------------------------------------------------------    

# def user_import(request):

#     context = start_view(request, app="user", view="user_import", noauth="app_home:index", 
#         perm="p_iam_access", noauthz="app_user:private")
#     if context["redirect"]:
#         return redirect(context["redirect"])
#     aaa = get_aaa(request)

#     if request.method == "POST":
#         form = UserUploadForm(request.POST, request.FILES)
#         if form.is_valid():

#             file = request.FILES["file"]

#             if file.multiple_chunks():
#                 messages.add_message(request, messages.ERROR, _("Import failed - file too big"))
#                 log(ERROR, aaa=aaa, app="user", view="user_import", action="POST", status="FAIL", data="File too big")   
#                 return redirect("app_user:user_list")          

#             elif file.name.endswith(".json"):
#                 line_total, line_ok = import_json(file)

#             elif file.name.endswith(".yaml"):
#                 line_total, line_ok = import_yaml(file)

#             elif file.name.endswith(".yml"):
#                 line_total, line_ok = import_yaml(file)

#             else:
#                 messages.add_message(request, messages.ERROR, _("Import failed - unknown file"))
#                 log(ERROR, aaa=aaa, app="user", view="user_import", action="POST", status="FAIL", data=f"Unknown file type {file}")
#                 return redirect("app_user:user_list")          

#             # OK
#             messages.add_message(request, messages.SUCCESS, _("Imported  : ") + f"{line_ok}/{line_total}" )    
#             log(INFO, aaa=aaa, app="user", view="user_import", action="POST", status="OK", data=f"{file} - {line_ok}/{line_total} entries")
#             return redirect("app_user:user_list")


#     # Failed
#     messages.add_message(request, messages.ERROR, _("Failed to import"))
#     log(ERROR, aaa=aaa, app="user", view="user_import", action="POST", status="FAIL", data="import failed, not a POST")
#     return redirect("app_user:user_list")


# ----------------------------------------------------------
# User Preferences
# ----------------------------------------------------------    

def preferences(request):
    ''' user  edit form'''

    context = start_view(request, app="iam", view="user_preferences", 
        noauth="app_sirene:index", perm="p_user_pref", noauthz="app_home:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]


    user = user_get_by_login(aaa["username"])
    if not user:
        return redirect("app_home:private")


    if request.method == "POST":

        form = UserPrefForm(request.POST)
        
        if form.is_valid():

            user.displayname = form.cleaned_data["displayname"]
            user.firstname = form.cleaned_data["firstname"]
            user.lastname = form.cleaned_data["firstname"]

            user.want_notifications = form.cleaned_data["want_notifications"]
            user.want_24 = form.cleaned_data["want_24"]
            user.want_email = form.cleaned_data["want_email"]
            user.want_sms = form.cleaned_data["want_sms"]
            user.secondary_email = form.cleaned_data["secondary_email"]
            user.secondary_mobile = form.cleaned_data["secondary_mobile"]

            user.last_update = timezone.now()
            
            try:
                user.save()
            except Exception as e:
                messages.add_message(request, messages.ERROR, _("Failed to edit preferences") )
                print(e)
                return redirect("app_home:private")

            messages.add_message(request, messages.SUCCESS, _("Preferences updated"))
            return redirect("app_home:private")

        else:
            messages.add_message(request, messages.ERROR, _("Invalid preferences"))

    else:

        initial = {}
        # won't be edited
        initial["is_enabled"] = user.is_enabled
        initial["email"] = user.email
        initial["mobile"] = user.mobile

        initial["firstname"] = user.firstname
        initial["lastname"] = user.lastname
        initial["displayname"] = user.displayname

        initial["want_notifications"] = user.want_notifications
        initial["want_24"] = user.want_24
        initial["want_email"] = user.want_email
        initial["want_sms"] = user.want_sms
        initial["secondary_email"] = user.secondary_email
        initial["secondary_mobile"] = user.secondary_mobile

        form = UserPrefForm(initial=initial)

    context["form"] = form
    context["user"] = user
    return render(request, 'app_user/user_pref_edit.html', context)


# -----------------------------------------
# logout
#-----------------------------------------

def logout(request):

    context = start_view(request, app="iam", view="logout", noauth="app_home:index")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]

    request.session.flush()

    # Django Auth mode
    if aaa["auth_mode"] == "local":
        django_logout(request)

    # basic / browser mode
    elif aaa["auth_mode"] == "basic":
        # HACK redirect with false credential
        #redirect("http://logout:logout@cavaliba.com")
        # user must erase his browser history
        pass

    # oauth2
    else:
        # already done by flush
        pass
            


    return redirect("app_home:index")


# -----------------------------------------
# debug user env
#-----------------------------------------
    
def debug_env(request):

    context = start_view(request, app="iam", view="debug_env", 
        noauth="app_sirene:index", perm="p_user_debug", noauthz="app_sirene:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]

    #print("DEBUG request.LANGUAGE_CODE=", request.LANGUAGE_CODE)
    #print("DEBUG get_language()=", get_language())


    str_headers = '' 
    for header, value in request.headers.items():
        str_headers += f"{header}: {value}\n"

    # decode JWT Access Token if any
    # ------------------------------
    encoded = request.headers.get('X-Access-Token','')
    # encoded = request.META.get('X-Access-Token')

    try:
        jwt_header = jwt.get_unverified_header(encoded)
        jwt_payload = jwt.decode(encoded, options={"verify_signature": False})
        jwt_header = json.dumps(jwt_header, indent=4)
        jwt_payload = json.dumps(jwt_payload, indent=4)
    except Exception as e:
        log(WARNING, aaa=aaa, app="iam", view="user", action="debug", status="KO", data=e)
        jwt_header = "n/a"
        jwt_payload = "n/a"

    aaa_nice = pprint.pformat(aaa)


    # OIDC Authorization Bearer Token
    # -------------------------------
    # request.META.get('HTTP_AUTHORIZATION') 
    # request.headers.get('Authorization')
    try:
        # Authorization: Bearer {access_token_here}
        autz_header = request.headers.get('Authorization','')
        encoded = autz_header.split()[1]
        tokid_header  = jwt.get_unverified_header(encoded)
        tokid_payload = jwt.decode(encoded, options={"verify_signature": False})
        tokid_header  = json.dumps(tokid_header, indent=4)
        tokid_payload = json.dumps(tokid_payload, indent=4)
    except Exception as e:
        log(WARNING, aaa=aaa, app="iam", view="user", action="debug", status="KO", data=e)
        tokid_header = "n/a"
        tokid_payload = "n/a"


    context["title"] = "Debug ENV"
    context["env"] = str_headers
    context["jwt_header"] = jwt_header
    context["jwt_payload"] = jwt_payload
    context["tokid_header"] = tokid_header
    context["tokid_payload"] = tokid_payload
    context["aaa_nice"] = aaa_nice
    
    return render(request, 'app_user/debug.html', context)

#-----------------------------------------
#  Send test email
#-----------------------------------------
def email_test(request, userid=None):


    context = start_view(request, app="iam", view="email_test", 
        noauth="app_sirene:index", perm="p_user_email_test", noauthz="app_sirene:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]


    subject = get_configuration("sirene","EMAIL_TEST_SUBJECT")
    text_content = get_configuration("sirene", "EMAIL_TEST_CONTENT")
    html_content = None

    dest = ""
    if userid:
        user = user_get_by_id(userid)
        if not user:
            return redirect("app_user:private")

    if request.method == "POST":

        dest = user_get_email(user)

        task_send_mail.delay(subject, text_content, [dest], html_content=html_content, aaa=aaa)
        messages.add_message(request, messages.SUCCESS, _("Test Email sent"))
    
    return redirect("app_user:private")

        

#-----------------------------------------
#  SMS Test form User Edit Form
#-----------------------------------------    
def sms_test(request, userid=None):

    context = start_view(request, app="iam", view="sms_test", 
        noauth="app_sirene:index", perm="p_user_sms_test", noauthz="app_user:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]


    if get_sms_quota(aaa) < 1:
        messages.add_message(request, messages.ERROR, _("Insufficient SMS Quota"))
        return redirect("app_user:private")

    if request.method == "POST":

        dest = ""
        if userid:
            user = user_get_by_id(userid)
            if not user:
                return redirect("app_user:private")

        dest = user_get_mobile(user)

        if not sms_check_valid_number(dest):
            messages.add_message(request, messages.ERROR, _("Invalid SMS number"))
            return redirect("app_user:private")

        data = get_configuration("sirene", "SMS_TEST")

        task_send_sms.delay([dest], data, aaa=aaa)
        messages.add_message(request, messages.SUCCESS, _("Test SMS sent"))

    return redirect("app_user:private")




#-----------------------------------------
# Impersonate
#-----------------------------------------    
def impersonate(request, newlogin=None):

    context = start_view(request, app="iam", view="impersonate", 
        noauth="app_sirene:index", perm="p_iam_impersonate", noauthz="app_user:private")
    if context["redirect"]:
        return redirect(context["redirect"])
    
    aaa = context["aaa"]

    try:
        impersonate_by = aaa["username"]
    except:
        return redirect("app_sirene:private")    

    if newlogin:
        
        # logout current user
        cache.init()
        request.session.flush()

        #  try relog with newlogin
        new_aaa = get_aaa(request, auth_mode="impersonate", impersonate_by=impersonate_by, impersonate=newlogin)
        
        # check newlogin is not admin
        if new_aaa["is_admin"]:
            messages.add_message(request, messages.ERROR, "Can't impersonate ADMIN")
            cache.init()
            request.session.flush()

        # success
        elif new_aaa["is_authenticated"]:
            request.session["aaa"] = new_aaa
            messages.add_message(request, messages.SUCCESS, "Impersonated for " + newlogin)
            return redirect("app_sirene:private")
        else:
            messages.add_message(request, messages.ERROR, "Invalid new login")
        
    else:
        messages.add_message(request, messages.ERROR, "No login provided")
        #return redirect("app_user:impersonate")
        return render(request, 'app_user/impersonate.html', context)


    return redirect("app_sirene:private")
