# (c) cavaliba.com - data - aaa.py

import base64
import datetime
import re
from pprint import pprint

from django.conf import settings
from django.utils import timezone

import app_home.cache as cache
from app_data.ip import get_user_ip, is_trusted_ip
from app_data.user import User
from app_home.configuration import get_configuration
from app_home.log import DEBUG, ERROR, INFO, WARNING, log
from app_user.models import SireneVisitor


def get_username(aaa=None):

    if aaa:
        return aaa.get("username", "auto")
    else:
        return "auto"


def update_last_login(aaa):

    if aaa["userid"]:
        last_login = aaa.get("last_login")
        if last_login:
            try:
                last_dt = datetime.datetime.strptime(last_login, "%Y-%m-%d %H:%M:%S")
                delta = datetime.datetime.utcnow() - last_dt
                if delta.total_seconds() < settings.CAVALIBA_LAST_LOGIN_MINUTES * 60:
                    return
            except Exception:
                pass
        user = User.from_id(id=aaa["userid"])
        user.update_last_login()

    elif aaa["is_visitor"]:
        username = aaa["username"]
        if not username:
            return
        if len(username) == 0:
            return

        visitor = SireneVisitor.objects.filter(username=username).first()
        if not visitor:
            visitor = SireneVisitor(username=username)

        visitor.last_login = timezone.now()
        visitor.user_ip = aaa.get("user_ip", "")
        visitor.save()
        return


def start_command(command="n/a", action="n/a"):

    cache.init()

    log(WARNING, app="command", view=command, action=action, data="start")

    return


def start_ajax(request):

    cache.init()

    if get_configuration(appname="env", keyname="CAVALIBA_DEBUG_AAA") == "yes":
        aaa = get_aaa(request)
        request.session["aaa"] = aaa
        pprint(aaa)
    else:
        if "aaa" in request.session:
            aaa = request.session["aaa"]
        else:
            aaa = get_aaa(request)
            request.session["aaa"] = aaa

    cache.cache_aaa = aaa

    context = {}
    context["aaa"] = aaa

    return context


def start_view(request, app="na", view="na", noauth=None, perm=None, noauthz=None, visitor=False):
    """returns a context w/ a redirect target if auth or autor i"""

    cache.init()

    debug_aaa = get_configuration(appname="env", keyname="CAVALIBA_DEBUG_AAA")
    cache_session = get_configuration(appname="user", keyname="CACHE_SESSION")

    if debug_aaa == "yes":
        print("---------------------------------------------------------------------------")
        print(f"START VIEW - start for app={app} - view={view}")

    if cache_session == "no":
        aaa = get_aaa(request)
        if debug_aaa == "yes":
            print("START VIEW - cache_session=no ; get_aaa() called")
    else:
        if "aaa" in request.session:
            aaa = request.session["aaa"]
            if debug_aaa == "yes":
                print("START VIEW - cache_session=yes ;  cache_hit ;  aaa in request.session")
        else:
            aaa = get_aaa(request)
            if debug_aaa == "yes":
                print("START VIEW - cache_session=yes ; cache_miss ;  get_aaa()")
            if aaa["is_authenticated"]:
                request.session["aaa"] = aaa
                if debug_aaa == "yes":
                    print("START VIEW - cache miss + authenticated=yes ; set request.session")
            else:
                request.session.flush()
                if debug_aaa == "yes":
                    print("START VIEW - cache_miss + authenticated = no ; flush session")

    cache.cache_aaa = aaa

    if debug_aaa == "yes":
        print("START VIEW - aaa :")
        print("  auth_mode:", aaa["auth_mode"])
        print("  impersonate_by:", aaa["impersonate_by"])
        print("  is_anonymous:", aaa["is_anonymous"])
        print("  is_authenticated:", aaa["is_authenticated"])
        print("  is_admin:", aaa["is_admin"])
        print("  is_trusted_ip:", aaa["is_trusted_ip"])
        print("  is_visitor:", aaa["is_visitor"])
        print("  user_ip:", aaa["user_ip"])
        print("  user_id:", aaa["userid"])
        print("  username:", aaa["username"])
        print("  last_login:", aaa["last_login"])
        print("  aaa: >>")
        pprint(aaa, compact=True, width=120)

    context = {}
    context["aaa"] = aaa
    context["redirect"] = None

    update_last_login(aaa)

    if noauth:
        if aaa["auth_mode"] == "local":
            if not (aaa["is_authenticated"] or (visitor and aaa["is_visitor"])):
                context["redirect"] = f"{settings.LOGIN_URL}?next={request.path}"
                if debug_aaa == "yes":
                    print("START VIEW - auth required, but local mode not authenticated")
                return context

        else:
            if not (aaa["is_authenticated"] or (visitor and aaa["is_visitor"])):
                if debug_aaa == "yes":
                    print("START VIEW - auth required, but not authenticated (other mode)")
                context["redirect"] = noauth
                return context

    if perm:
        if perm not in aaa["perms"]:
            log(WARNING, aaa=aaa, app=app, view=view, action="access", data="Not allowed")
            context["redirect"] = noauthz
            return context

    log(DEBUG, aaa=aaa, app=app, view=view, action="start_view", status="OK")

    return context


def get_aaa(request, auth_mode=None, impersonate=None, impersonate_by=None):

    debug_aaa = get_configuration(appname="env", keyname="CAVALIBA_DEBUG_AAA")

    if len(cache.cache_aaa) > 0:
        if debug_aaa == "yes":
            print("DEBUG AAA - get_aaa() from cache.cache_aaa.")
        return cache.cache_aaa

    aaa = {}

    uname = ""
    email = ""

    aaa["is_trusted_ip"] = False
    aaa["is_anonymous"] = True
    aaa["is_authenticated"] = False
    aaa["is_visitor"] = False
    aaa["is_admin"] = False

    aaa["username"] = "unknown"
    aaa["user"] = None
    aaa["userid"] = None

    aaa["groups"] = []
    aaa["groups_direct"] = []
    aaa["groups_computed"] = []
    aaa["groups_indirect"] = []
    aaa["perms"] = []
    aaa["roles"] = []

    aaa["user_ip"] = get_user_ip(request)
    aaa["is_trusted_ip"] = is_trusted_ip(aaa["user_ip"])

    aaa["impersonate_by"] = ""
    aaa["last_login"] = None

    if not auth_mode:
        auth_mode = get_configuration(appname="env", keyname="CAVALIBA_AUTH_MODE")

    aaa["auth_mode"] = auth_mode

    if debug_aaa == "yes":
        print("DEBUG_AAA - auth_mode: ", auth_mode)

    if auth_mode == "basic":
        header = request.META.get("HTTP_AUTHORIZATION")
        try:
            auth = header.split()
        except Exception:
            auth = ""
            uname = ""
        if len(auth) == 2:
            if auth[0].lower() == "basic":
                myenc = auth[1]
                myenc = bytes(myenc, encoding="utf-8")
                uname, pwd = str(base64.b64decode(myenc)).split(":")
                uname = uname[2:]

    elif auth_mode == "oauth2":
        login_field = get_configuration(appname="user", keyname="AUTH_FEDERATED_LOGIN_FIELD")
        uname = request.headers.get(login_field)
        email_field = get_configuration(appname="user", keyname="AUTH_FEDERATED_EMAIL_FIELD")
        email = request.headers.get(email_field)

        if debug_aaa == "yes":
            print("DEBUG_AAA - oauth2 login_field / uname ", login_field, uname)
            print("DEBUG_AAA - oauth2 email_field / email ", email_field, email)

    elif auth_mode == "local":
        if request.user.is_authenticated:
            uname = request.user.username
            aaa["is_anonymous"] = False
            if debug_aaa == "yes":
                print("DEBUG_AAA - auth_mode local - anonymous=False, request.user.username", uname)
        else:
            if debug_aaa == "yes":
                print(
                    "DEBUG_AAA - auth_mode local - no request.user.is_authenticated , no username"
                )

    elif auth_mode == "unittest":
        uname = "unittest"
        aaa["is_anonymous"] = False

    elif auth_mode == "forced":
        force_user = get_configuration(appname="env", keyname="CAVALIBA_FORCE_LOGIN")
        if debug_aaa == "yes":
            print("DEBUG_AAA - force_user ", force_user)
        if len(force_user) > 0:
            uname = force_user
            aaa["is_anonymous"] = False
            if debug_aaa == "yes":
                print("DEBUG_AAA - force_user ; is_anonymous=False")
        else:
            aaa["is_anonymous"] = True
            if debug_aaa == "yes":
                print("DEBUG_AAA - force_user ; is_anonymous=True. DONE.")
            return aaa

    elif auth_mode == "impersonate":
        uname = impersonate
        aaa["impersonate_by"] = impersonate_by

    if debug_aaa == "yes":
        print("DEBUG_AAA - uname=", uname)

    if not uname:
        aaa["is_anonymous"] = True
        if debug_aaa == "yes":
            print("DEBUG_AAA - no uname ; is_anonymous=True. DONE.")
        return aaa

    if len(uname) == 0:
        aaa["is_anonymous"] = True
        if debug_aaa == "yes":
            print("DEBUG_AAA - len(uname)=0 ; is_anonymous=True. DONE.")
        return aaa

    aaa["is_anonymous"] = False
    if debug_aaa == "yes":
        print("DEBUG_AAA - is_anonymous=False, uname=", uname)

    truncate_login = get_configuration(appname="user", keyname="AUTH_LOGIN_REMOVE_DOMAIN")
    if truncate_login == "yes":
        if "@" in uname:
            uname = re.sub("@(.*)$", "", uname)
            if debug_aaa == "yes":
                print("DEBUG_AAA - truncated domain name in login ; uname=", uname)

    aaa["username"] = uname

    aaa["is_visitor"] = True
    aaa["is_authenticated"] = False

    user = User.from_keyname(keyname=uname)

    if debug_aaa == "yes":
        print(f"DEBUG_AAA - user: {user}")

    jit = get_configuration(appname="user", keyname="AUTH_PROVISIONING")
    if debug_aaa == "yes":
        print("DEBUG_AAA - jit config: ", jit)

    if user:
        aaa["is_visitor"] = False
        aaa["is_authenticated"] = True
        if debug_aaa == "yes":
            print("DEBUG_AAA - user found in DB ; visitor=False, authenticated=True")

        if not user.is_enabled:
            aaa["is_authenticated"] = False
            if debug_aaa == "yes":
                print("DEBUG_AAA - user disabled ; authenticated=False. DONE.")
            return aaa

    else:
        if debug_aaa == "yes":
            print("DEBUG_AAA - user NOT in DB , assuming visitor for now")

        if jit == "visitor":
            aaa["is_visitor"] = True
            aaa["is_authenticated"] = False
            if debug_aaa == "yes":
                print(f"DEBUG_AAA - jit ({jit}), visitor=True, authenticated=False")

        elif jit == "create" or jit == "sync":
            user = User(keyname=uname)
            if email:
                user.set_field_value_single(fieldname="email", value=email)
            user.create()

            if user:
                log(INFO, aaa=aaa, data=f"JIT - ({jit}) get_aaa JIT user created in DB: {uname}")
                aaa["is_visitor"] = False
                aaa["is_authenticated"] = True
                if debug_aaa == "yes":
                    print(
                        f"DEBUG_AAA - jit ({jit}) user created. visitor=False. authenticated=True"
                    )
            else:
                log(ERROR, aaa=aaa, data=f"JIT ({jit}) failed to create user in DB: {uname}")
                if debug_aaa == "yes":
                    print(
                        f"DEBUG_AAA - jit ({jit}) user not created. visitor=True. authenticated=False"
                    )
                aaa["is_visitor"] = True
                aaa["is_authenticated"] = True
                return aaa

        else:
            aaa["is_visitor"] = False
            aaa["is_authenticated"] = False
            if debug_aaa == "yes":
                print(f"DEBUG_AAA - not in DB, no jit or jit other: ({jit}). DONE")
            return aaa

    if user:
        aaa["userid"] = user.id
        aaa["user"] = user.to_dict()
        aaa["last_login"] = user.last_login
        if debug_aaa == "yes":
            print("DEBUG_AAA - aaa[user] attributes populated with DB content.")

    if user:
        try:
            user.compute_aaa()
        except Exception:
            pass

        aaa["perms"] = [p.keyname for p in user.permissions]
        aaa["groups_direct"] = [g.keyname for g in user.direct_groups]
        aaa["groups_computed"] = [g.keyname for g in user.auto_groups]
        aaa["groups_indirect"] = [g.keyname for g in user.parent_groups]
        aaa["groups"] = [g.keyname for g in user.groups]
        aaa["roles"] = [r.keyname for r in user.roles]

    if debug_aaa == "yes":
        print("DEBUG_AAA - groups computed: ", aaa["groups"])
        print("DEBUG_AAA - roles  computed: ", aaa["roles"])
        print("DEBUG_AAA - perms  computed: ", aaa["perms"])

    if aaa["username"] == "admin" or "role_admin" in aaa["roles"]:
        aaa["is_admin"] = True
        if debug_aaa == "yes":
            print("DEBUG_AAA - is_admin=True")

    if debug_aaa == "yes":
        print("DEBUG_AAA - get_aaa() end, DONE.")

    return aaa
