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

"""
Permission checks (aaa["perms"] membership tests for schema/iam/user/group/role/
permission CRUD) plus SirenePermission CRUD, bootstrap/load (import), and export
(list-dict/JSON/YAML) helpers.

Method inventory:

aaa helper:
    aaa_perms_valid

Permission checks:
    has_schema_read_permission, has_schema_write_permission,
    has_iam_export_permission, has_user_read_permission,
    has_user_create_permission, has_user_update_permission,
    has_user_delete_permission, has_group_read_permission,
    has_group_create_permission, has_group_update_permission,
    has_group_delete_permission, has_role_read_permission,
    has_role_create_permission, has_role_update_permission,
    has_role_delete_permission, has_permission_read_permission,
    has_permission_create_permission, has_permission_update_permission,
    has_permission_delete_permission

Permission CRUD:
    permission_all_keynames, permission_get_all, permission_search,
    permission_get_by_id, permission_get_by_name, permission_get_by_data,
    permission_init, permission_create, permission_update,
    permission_update_by_data, permission_delete, permission_delete_by_id,
    permission_delete_by_data

Bootstrap / import:
    bootstrap_permissions, load_permission

Export:
    permission_listdict_format, permission_json_response,
    _PermissionYamlDumper.write_line_break, permission_yaml_response
"""

import json
import re

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


#  -------
# aaa
#  -------
def aaa_perms_valid(aaa):
    """Return True if aaa carries a usable perms list."""
    try:
        if type(aaa["perms"]) is not list:
            return False
    except Exception:
        return False
    return True


#  -------
# SCHEMA
#  -------
def has_schema_read_permission(aaa=None):
    """permission to read all schemas definitions and fields"""

    if not aaa_perms_valid(aaa):
        return False

    if "p_schema_read" in aaa["perms"]:
        return True
    if "p_schema_write" in aaa["perms"]:
        return True
    return False


def has_schema_write_permission(aaa=None):
    """permission to write all schemas definitions and fields"""

    if not aaa_perms_valid(aaa):
        return False

    if "p_schema_write" in aaa["perms"]:
        return True
    return False


#  ----
# IAM
#  ----
def has_iam_export_permission(aaa=None):
    """Check p_iam_export in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_iam_export" in aaa["perms"]:
        return True
    return False


def has_user_read_permission(aaa=None):
    """Check p_user_read in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_user_read" in aaa["perms"]:
        return True
    return False


def has_user_create_permission(aaa=None):
    """Check p_user_create in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_user_create" in aaa["perms"]:
        return True
    return False


def has_user_update_permission(aaa=None):
    """Check p_user_update in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_user_update" in aaa["perms"]:
        return True
    return False


def has_user_delete_permission(aaa=None):
    """Check p_user_delete in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_user_delete" in aaa["perms"]:
        return True
    return False


def has_group_read_permission(aaa=None):
    """Check p_group_read in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_group_read" in aaa["perms"]:
        return True
    return False


def has_group_create_permission(aaa=None):
    """Check p_group_create in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_group_create" in aaa["perms"]:
        return True
    return False


def has_group_update_permission(aaa=None):
    """Check p_group_update in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_group_update" in aaa["perms"]:
        return True
    return False


def has_group_delete_permission(aaa=None):
    """Check p_group_delete in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_group_delete" in aaa["perms"]:
        return True
    return False


def has_role_read_permission(aaa=None):
    """Check p_role_read in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_role_read" in aaa["perms"]:
        return True
    return False


def has_role_create_permission(aaa=None):
    """Check p_role_create in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_role_create" in aaa["perms"]:
        return True
    return False


def has_role_update_permission(aaa=None):
    """Check p_role_update in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_role_update" in aaa["perms"]:
        return True
    return False


def has_role_delete_permission(aaa=None):
    """Check p_role_delete in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_role_delete" in aaa["perms"]:
        return True
    return False


def has_permission_read_permission(aaa=None):
    """Check p_permission_read in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_permission_read" in aaa["perms"]:
        return True
    return False


def has_permission_create_permission(aaa=None):
    """Check p_permission_create in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_permission_create" in aaa["perms"]:
        return True
    return False


def has_permission_update_permission(aaa=None):
    """Check p_permission_update in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_permission_update" in aaa["perms"]:
        return True
    return False


def has_permission_delete_permission(aaa=None):
    """Check p_permission_delete in aaa["perms"]."""
    if not aaa_perms_valid(aaa):
        return False
    if "p_permission_delete" in aaa["perms"]:
        return True
    return False


# -------------------------------------------------------
# PERMISSION CRUD
# -------------------------------------------------------


def permission_all_keynames():
    """Return the keyname of every SirenePermission."""
    from app_user.models import SirenePermission

    perms = []
    for p in SirenePermission.objects.all():
        perms.append(p.keyname)
    return perms


def permission_get_all():
    """Return a queryset of every SirenePermission."""
    from app_user.models import SirenePermission

    return SirenePermission.objects.all()


def permission_search(query=None, page=1, size=10):
    """Return a paginated queryset of SirenePermission matching query against
    keyname/displayname/description (or all if query is empty); [] if query
    contains disallowed characters."""
    from django.db.models import Q

    from app_user.models import SirenePermission

    m = re.compile(r"[a-zA-Z0-9()_/.-]*$")
    if query and not m.match(query):
        return []

    offset = (page - 1) * size
    limit = offset + size

    qs = SirenePermission.objects.order_by("keyname")
    if query:
        qs = qs.filter(
            Q(keyname__icontains=query)
            | Q(displayname__icontains=query)
            | Q(description__icontains=query)
        )
    return qs[offset:limit]


def permission_get_by_id(pid):
    """Return the SirenePermission with this pk, or None."""
    from app_user.models import SirenePermission

    return SirenePermission.objects.filter(pk=pid).first()


def permission_get_by_name(keyname):
    """Return the SirenePermission with this keyname, or None."""
    from app_user.models import SirenePermission

    return SirenePermission.objects.filter(keyname=keyname).first()


def permission_get_by_data(data):
    """Return the SirenePermission matching data["keyname"], or None."""
    keyname = data.get("keyname", None)
    if not keyname:
        return
    return permission_get_by_name(keyname)


def permission_init(data):
    """Return the existing SirenePermission for data["keyname"] if any, else create
    and populate a new one from data. Returns (obj_or_None, error_or_None)."""
    from app_user.models import SirenePermission

    keyname = data.get("keyname", None)
    if not keyname:
        return None, "permission_init: missing keyname"
    entry = permission_get_by_name(keyname)
    if entry:
        return entry, None
    entry = SirenePermission()
    return permission_update(entry, data)


def permission_create(data):
    """Create (or reuse) and populate a SirenePermission from data["keyname"].
    Returns (obj_or_None, error_or_None)."""
    from app_user.models import SirenePermission

    keyname = data.get("keyname", None)
    if not keyname:
        return None, "permission_create: missing keyname"
    entry = permission_get_by_name(keyname)
    if not entry:
        entry = SirenePermission()
    return permission_update(entry, data)


def permission_update(iobj, data):
    """Set keyname/displayname/description/is_builtin from data onto iobj and save.
    Returns (obj_or_None, error_or_None)."""
    if not iobj:
        return None, "permission_update: obj not found"
    if not data:
        return None, "permission_update: data not found"

    for attrib in ["keyname", "displayname", "description", "is_builtin"]:
        if attrib in data:
            try:
                setattr(iobj, attrib, data[attrib])
            except Exception:
                pass

    iobj.last_update = timezone.now()
    iobj.save()
    return iobj, None


def permission_update_by_data(data):
    """Resolve the SirenePermission from data["keyname"] and apply permission_update.
    Returns (obj_or_None, error_or_None)."""
    iobj = permission_get_by_data(data)
    if iobj:
        return permission_update(iobj, data)
    return None, "permission_update: not found"


def permission_delete(iobj):
    """Delete iobj unless it's a builtin permission. Returns (obj_or_False, error_or_None)."""
    if not iobj:
        return False, "permission_delete: not found"
    if iobj.is_builtin:
        return False, "permission_delete: is builtin"
    try:
        iobj.delete()
        return iobj, None
    except Exception as e:
        return False, f"permission_delete: {e}"


def permission_delete_by_id(pid):
    """Delete the SirenePermission with this pk (unless builtin). Returns (obj_or_False, error_or_None)."""
    iobj = permission_get_by_id(pid)
    return permission_delete(iobj)


def permission_delete_by_data(data):
    """Delete the SirenePermission matching data["keyname"] (unless builtin).
    Returns (obj_or_False, error_or_None)."""
    iobj = permission_get_by_data(data)
    return permission_delete(iobj)


def bootstrap_permissions(datadict):
    """Create/update (is_builtin=True) or delete SirenePermission rows from a list
    of {keyname, description, _action} dicts, e.g. from 00_permission.yml. Returns
    the number of entries processed."""
    from app_home.log import WARNING, log
    from app_user.models import SirenePermission

    for data in datadict:
        keyname = data.get("keyname", None)
        if not keyname:
            continue

        if "_action" in data:
            if data["_action"] == "delete":
                try:
                    SirenePermission.objects.filter(keyname=keyname).delete()
                except Exception as e:
                    print("ERR: ", e)
                continue

        entry = SirenePermission.objects.filter(keyname=keyname).first()
        if not entry:
            entry = SirenePermission()

        entry.is_builtin = True
        try:
            entry.keyname = data["keyname"]
            entry.displayname = data["keyname"]
        except Exception:
            pass
        try:
            entry.description = data["description"]
        except Exception:
            pass
        entry.save()

    count = len(datadict)
    log(
        WARNING,
        app="iam",
        view="permission",
        action="bootstrap",
        status="OK",
        data=f"{count} permissions",
    )
    return count


def load_permission(datadict=None, verbose=None, aaa=None):
    """Apply one permission import entry (datadict["_action"]: init/create/update/
    delete), gated on the matching has_permission_*_permission check for aaa.
    Returns None on success, else an error message string."""
    if not datadict:
        return "load_permission: no data"

    if not aaa:
        from app_home.log import WARNING, log

        log(
            WARNING,
            aaa=aaa,
            app="iam",
            view="permission",
            action="load",
            status="DENY",
            data=_("Not allowed"),
        )
        return "load_permission: no permission"

    keyname = datadict.get("keyname", None)
    if not keyname:
        return "load_permission: no keyname"

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

    if action == "init":
        if not has_permission_create_permission(aaa=aaa):
            from app_home.log import WARNING, log

            log(
                WARNING,
                aaa=aaa,
                app="iam",
                view="permission",
                action="init",
                status="DENY",
                data=_("Not allowed"),
            )
            return f"load_permission: init not allowed for {keyname}"
        perm, err = permission_init(datadict)

    elif action == "create":
        if not has_permission_create_permission(aaa=aaa):
            from app_home.log import WARNING, log

            log(
                WARNING,
                aaa=aaa,
                app="iam",
                view="permission",
                action="create",
                status="DENY",
                data=_("Not allowed"),
            )
            return f"load_permission: create not allowed for {keyname}"
        perm, err = permission_create(datadict)

    elif action == "update":
        if not has_permission_update_permission(aaa=aaa):
            from app_home.log import WARNING, log

            log(
                WARNING,
                aaa=aaa,
                app="iam",
                view="permission",
                action="update",
                status="DENY",
                data=_("Not allowed"),
            )
            return f"load_permission: update not allowed for {keyname}"
        perm, err = permission_update_by_data(datadict)

    elif action == "delete":
        if not has_permission_delete_permission(aaa=aaa):
            from app_home.log import WARNING, log

            log(
                WARNING,
                aaa=aaa,
                app="iam",
                view="permission",
                action="delete",
                status="DENY",
                data=_("Not allowed"),
            )
            return f"load_permission: delete not allowed for {keyname}"
        perm, err = permission_delete_by_data(datadict)

    else:
        return f"load_permission: unknown action: {action}"

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


# -------------------------------------------------------
# EXPORT
# -------------------------------------------------------


def permission_listdict_format(permissions):
    """Convert an iterable of SirenePermission into a list of exportable dicts
    (classname="_permission", id, and non-empty/non-False attributes only)."""
    dict_attributs = ["keyname", "is_builtin", "displayname", "is_enabled", "description"]
    datalist = []
    for permission in permissions:
        m = model_to_dict(permission, fields=dict_attributs)
        m["classname"] = "_permission"
        m2 = {}
        for k, v in m.items():
            if not isinstance(v, bool):
                if v:
                    m2[k] = v
            else:
                m2[k] = v
        m2["id"] = permission.id
        datalist.append(m2)
    return datalist


def permission_json_response(items):
    """Return items as a downloadable "permissions.json" HttpResponse."""
    datalist = permission_listdict_format(items)
    filedata = json.dumps(datalist, indent=4, ensure_ascii=False)
    response = HttpResponse(filedata, content_type="text/json")
    response["Content-Disposition"] = 'attachment; filename="permissions.json"'
    return response


class _PermissionYamlDumper(yaml.SafeDumper):
    def write_line_break(self, data=None):
        """Emit an extra blank line after top-level YAML blocks for readability."""
        super().write_line_break(data)
        if len(self.indents) < 2:
            super().write_line_break()


def permission_yaml_response(items):
    """Return items as a downloadable "permissions.yaml" HttpResponse."""
    datalist = permission_listdict_format(items)
    filedata = yaml.dump(
        datalist, allow_unicode=True, Dumper=_PermissionYamlDumper, sort_keys=False
    )
    response = HttpResponse(filedata, content_type="text/yaml")
    response["Content-Disposition"] = 'attachment; filename="permissions.yaml"'
    return response
