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

import csv
import json
import os

import yaml
from django.utils.translation import gettext as _

from app_data.data import Instance
from app_data.permissions import has_schema_write_permission, load_permission

# from app_data.data import load_instance, load_schema
from app_data.pipeline import Pipeline
from app_data.schema import Schema
from app_data.user import User
from app_home.configuration import get_configuration
from app_home.load import load_home
from app_home.log import WARNING, log


# ---------------------------------------------------------------------
# Load broker for one or more objects provided as a LIST of DICTs
# ---------------------------------------------------------------------
def load_broker(datalist=None, aaa=None, force_action=None):
    """
    Load a batch of object into the database

    IN:
       datalist: [ {}, {}, {}, ... ]
       aaa:
       force_action: None, create, update, delete, init, enable, disable

    OUT:
       reply struct (dict) : count, count_ok, count_ko, errors=[]
    """

    reply = {
        "count": 0,
        "count_ok": 0,
        "count_ko": 0,
        "errors": [],
    }

    if not datalist:
        reply["errors"].append("no data provided")
        return reply

    if type(datalist) is not list:
        reply["errors"].append("invalid data provided (not a list)")
        return reply

    if not aaa:
        reply["errors"].append("missing identity")
        return reply

    if "perms" not in aaa:
        reply["errors"].append("missing aaa permissions")
        return reply

    for datadict in datalist:
        err = None

        reply["count"] += 1

        if type(datadict) is not dict:
            reply["count_ko"] += 1
            reply["errors"].append(f"invalid entry: {datadict}")
            continue

        classname = datadict.get("classname", None)
        if not classname:
            reply["count_ko"] += 1
            reply["errors"].append(f"missing classname in {datadict}")
            continue

        if len(classname) == 0:
            # unknown object class : NEXT : log error
            reply["count_ko"] += 1
            reply["errors"].append(f"invalid classname in {datadict}")
            continue

        if force_action:
            datadict["_action"] = force_action

        # load per schema
        if classname == "_schema":
            err = load_schema(datadict=datadict, aaa=aaa)

        # left as regular DB/ORM
        elif classname == "_permission":
            err = load_permission(datadict=datadict, aaa=aaa)

        elif classname == "_home":
            err = load_home(datadict=datadict, aaa=aaa)

        else:
            # internal: _pipeline, _dataview, _apikey, _enumerate
            # apps: sirene_*, ipam_*, status_monitor, ...
            # user-defined schema
            # v4.0 - user, group, role
            err = load_instance(datadict=datadict, aaa=aaa)

        if err:
            reply["count_ko"] += 1
            reply["errors"].append(err)
        else:
            reply["count_ok"] += 1

    return reply


# --------------------------------------------------------
# LOADER / IMPORT
# Global LOADER : class, schema, instance, static
# --------------------------------------------------------


def load_schema(datadict=None, verbose=True, aaa=None):
    """
    IN:
         datadict['keyname']  : mandatory classname(!)

    OUT:
        None or error string
    """

    if not datadict:
        return "load_schema: no data"

    if not has_schema_write_permission(aaa=aaa):
        log(
            WARNING,
            aaa=aaa,
            app="data",
            view="schema",
            action="load",
            status="DENY",
            data=_("Not allowed"),
        )
        return "load_schema: not allowed"

    #  for a _schema, classname is the provided keyname (classname = _schema)
    classname = datadict.get("keyname", None)
    if not classname:
        return f"load_schema: missing classname {datadict}"

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

    # delete Schema and Fields
    if action == "delete":
        # TODO: check classname is not builtin (_reserved)
        schema_obj = Schema.delete(classname)
        if schema_obj:
            return
        else:
            return f"load_schema: failed to delete {classname}"

    #  enable
    elif action == "enable":
        # TODO: check classname is not builtin (_reserved)
        schema_obj = Schema.enable(classname)
        if schema_obj:
            return
        else:
            return f"load_schema: failed to enable {classname}"

    # disable
    elif action == "disable":
        # TODO: check classname is not builtin (_reserved)
        schema_obj = Schema.disable(classname)
        if schema_obj:
            return
        else:
            return f"load_schema: failed to disable {classname}"

    # init
    elif action == "init":
        if Schema.exists(classname):
            return
        schema = Schema()
        schema.update_from_dict(datadict, verbose=verbose)
        schema.save()
        # TODO: check save result
        return

    # create (or update)
    elif action == "create":
        schema = Schema.from_name(classname)
        if not schema:
            schema = Schema()
        schema.update_from_dict(datadict, verbose=verbose)
        schema.save()
        return

    # update (only)
    elif action == "update":
        schema = Schema.from_name(classname)
        if not schema:
            return f"load_schema: not found: {classname} "
        schema.update_from_dict(datadict, verbose=verbose)
        schema.save()
        return

    elif action == "noop":
        return

    else:
        log(
            WARNING,
            aaa=aaa,
            app="data",
            view="schema",
            action="unknown",
            status="FAILED",
            data=f"Unknown load action {action} for schema {classname}",
        )

    return f"load_schema: unknown action {action} for {classname}"


def load_instance(datadict=None, aaa=None):

    if not datadict:
        return "load_instance: no data"

    if not aaa:
        log(
            WARNING,
            aaa=aaa,
            app="data",
            view="instance",
            action="load",
            status="DENY",
            data=_("Not allowed"),
        )
        return "load_instance: not allowed"

    classname = datadict.get("classname", None)
    schema = Schema.from_name(classname)
    if not schema:
        return f"load_instance: schema not found: {classname}"

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

    keyname = datadict.get("keyname", None)

    # v4.0 -  login backward compatibility for user (login is keyname)
    if not keyname:
        if classname == "user":
            keyname = datadict.get("login", None)

    # schema/user: keyname is an email => resolve to login
    if classname == "user" and keyname and "@" in keyname:
        user = User.get_by_email(email=keyname)
        if not user:
            return f"load_instance: user not found by email {keyname}"
        keyname = user.keyname
        datadict = {**datadict, "keyname": keyname}

    if action == "delete":
        instance = Instance.from_keyname(classname=classname, keyname=keyname, expand=False)
        if instance:
            if instance.has_delete_permission(aaa=aaa):
                instance.delete()
                return
            else:
                log(
                    WARNING,
                    aaa=aaa,
                    app="data",
                    view="instance",
                    action=action,
                    status="DENY",
                    data=_("Not allowed"),
                )
        else:
            log(
                WARNING,
                aaa=aaa,
                app="data",
                view="instance",
                action=action,
                status="DENY",
                data=_("Not found"),
            )
        return f"load_instance: delete failed for {classname}:{keyname}"

    elif action == "disable":
        instance = Instance.from_keyname(classname=classname, keyname=keyname, expand=False)
        if instance:
            if instance.has_update_permission(aaa=aaa):
                instance.disable()
                return
            else:
                log(
                    WARNING,
                    aaa=aaa,
                    app="data",
                    view="instance",
                    action=action,
                    status="DENY",
                    data=_("Not allowed"),
                )
        else:
            log(
                WARNING,
                aaa=aaa,
                app="data",
                view="instance",
                action=action,
                status="DENY",
                data=_("Not found"),
            )
        return f"load_instance: disable failed for {classname}:{keyname}"

    elif action == "enable":
        instance = Instance.from_keyname(classname=classname, keyname=keyname, expand=False)
        if instance:
            if instance.has_update_permission(aaa=aaa):
                instance.enable()
                return
            else:
                log(
                    WARNING,
                    aaa=aaa,
                    app="data",
                    view="instance",
                    action=action,
                    status="DENY",
                    data=_("Not allowed"),
                )
        else:
            log(
                WARNING,
                aaa=aaa,
                app="data",
                view="instance",
                action=action,
                status="DENY",
                data=_("Not found"),
            )
        return f"load_instance: enable failed for {classname}:{keyname}"

    # init only == new instance if doesn't already exist
    elif action == "init":
        instance = Instance.from_keyname(classname=classname, keyname=keyname, expand=False)
        if instance:
            log(
                WARNING,
                aaa=aaa,
                app="data",
                view="instance",
                action=action,
                status="DENY",
                data=_("Already exists"),
            )
            return
        instance = Instance(classname=classname)
        if instance:
            if schema.has_create_permission(aaa):
                instance.merge_import(datadict)
                instance.init()
                return
            else:
                log(
                    WARNING,
                    aaa=aaa,
                    app="data",
                    view="instance",
                    action=action,
                    status="DENY",
                    data=_("Not allowed"),
                )
                return f"load_instance: init not allowed for {classname}:{keyname}"
        else:
            log(
                WARNING,
                aaa=aaa,
                app="data",
                view="instance",
                action=action,
                status="DENY",
                data=_("Couldn't create"),
            )
        return f"load_instance: init failed for {classname}:{keyname}"

    # create and/or update if exists
    elif action == "create":
        instance = Instance.from_keyname(classname=classname, keyname=keyname, expand=False)
        if not instance:
            instance = Instance(classname=classname)
        if instance:
            if schema.has_create_permission(aaa):
                instance.merge_import(datadict)
                instance.create()
                return
            else:
                log(
                    WARNING,
                    aaa=aaa,
                    app="data",
                    view="instance",
                    action=action,
                    status="DENY",
                    data=_("Not allowed"),
                )
                return f"load_instance: create not allowed for {classname}:{keyname}"
        else:
            log(
                WARNING,
                aaa=aaa,
                app="data",
                view="instance",
                action=action,
                status="DENY",
                data=_("Couldn't create"),
            )
        return f"load_instance: create failed for {classname}:{keyname}"

    # don't create, update only if exists
    elif action == "update":
        instance = Instance.from_keyname(classname=classname, keyname=keyname, expand=False)
        if instance:
            if instance.has_update_permission(aaa=aaa):
                instance.merge_import(datadict)
                instance.update()
                return
            else:
                log(
                    WARNING,
                    aaa=aaa,
                    app="data",
                    view="instance",
                    action=action,
                    status="DENY",
                    data=_("Not allowed"),
                )
                return f"load_instance: update not allowed for {classname}:{keyname}"
        else:
            log(
                WARNING,
                aaa=aaa,
                app="data",
                view="instance",
                action=action,
                status="DENY",
                data=_("Not found"),
            )
        return f"load_instance: update failed for {classname}:{keyname}"

    elif action == "noop":
        return

    # unknown action
    else:
        log(
            WARNING,
            aaa=aaa,
            app="data",
            view="instance",
            action=action,
            status="DENY",
            data=_("Unknown action"),
        )

    return f"load_instance: unknown action {action} for {classname}:{keyname}"


# ---------------------------------------------------------------------
# CSV File to DATA
# ---------------------------------------------------------------------

# # custom processing of CSV file with header line and composit field
# def make_dictlist_from_csv(filedata,  splitfields=[]):

#     lines = filedata.splitlines()
#     header = []
#     rows = []
#     line_count = 0

#     delimiter = get_configuration(appname="home", keyname="CSV_DELIMITER")

#     for line in lines:
#         entry = {}
#         fields = line.split(delimiter)
#         if line_count == 0:
#             header = fields
#             line_count = 1
#         else:
#             line_count += 1
#             for i in range(0,len(header)):
#                 hi = header[i]
#                 if hi in splitfields:
#                     try:
#                         a = fields[i].split("+")
#                         entry[hi] = a
#                     except Exception:
#                         pass
#                 else:
#                     try:
#                         entry[hi] = fields[i]
#                     except Exception:
#                         pass
#             rows.append(entry)

#     return rows


def load_file_csv(file=None, filename=None, pipeline_name=None, verbose=None, first=1, last=0):
    """
    pipeline_name = keyname of pipeline object

    output: list of dict = [ {} , {}, {} ... ]
    {
        classname: _home|user|group|role|_permisison|_schema|_enumerate|_dataview|_pipeline| ...
            _sirene_category|_sirene_public|_sirene_template
            CLASSNAME
        keyname: my key
        displayname: "a nice display name"
        is_enabled:
        (...)
    }
    """

    if not pipeline_name:
        print("SKIP - missing pipeline")
        return

    pipeline = Pipeline.from_name(pipeline_name)
    if not pipeline:
        print("SKIP - invalid pipeline: ", pipeline_name)
        return

    classname = pipeline.classname
    # classname = pipeline_data.get("classname", None)
    if not classname:
        print("SKIP - missing classname in pipeline: ", pipeline_name)
        return

    # keyfield = pipeline_data.get("keyfield", "keyname")
    keyfield = pipeline.keyfield

    # csv_delimiter = pipeline_data.get("csv_delimiter", None)
    csv_delimiter = pipeline.csv_delimiter
    if not csv_delimiter:
        csv_delimiter = get_configuration(appname="home", keyname="CSV_DELIMITER")

    # encoding = pipeline_data.get("encoding", "utf-8")
    encoding = pipeline.encoding

    datalist = []

    # from file
    if file:
        csv_reader = csv.DictReader(file, delimiter=csv_delimiter)

        cursor = 0

        for entry in csv_reader:
            cursor += 1
            if cursor < first:
                continue
            if last > 0:
                if cursor > last:
                    break

            keyname = entry.get(keyfield, None)
            if keyname:
                entry["classname"] = classname
                entry["keyname"] = keyname
                datalist.append(entry)
            else:
                print("    !skip entry (no keyname/keyfield)")

    # from filename
    else:
        if not os.path.isfile(filename):
            print(f"SKIP - not a file ({filename})")
            return

        with open(filename, encoding=encoding) as file:
            # NEXT : handle CSV without header line (use pipeline structure)
            # filedata = file.read().decode("utf-8")

            csv_reader = csv.DictReader(file, delimiter=csv_delimiter)

            cursor = 0

            for entry in csv_reader:
                cursor += 1
                if cursor < first:
                    continue
                if last > 0:
                    if cursor > last:
                        break

                keyname = entry.get(keyfield, None)
                if keyname:
                    entry["classname"] = classname
                    entry["keyname"] = keyname
                    datalist.append(entry)
                else:
                    print("    !skip entry (no keyname/keyfield)")

    # cleanup empty field
    # for entry in datalist:
    #     for fieldname, fielddata in entry.items():
    #         print("**** ", fieldname, fielddata)

    return datalist


# ---------------------------------------------------------------------
# JSON to DATA
# ---------------------------------------------------------------------


def load_file_json(file=None, filename=None, pipeline_name=None, verbose=None):

    data = []

    # TODO: pipeline

    # from file
    if file:
        try:
            data = json.load(file)
        except Exception as e:
            print(f"SKIP - JSON syntax error in file : {e}")
            return

    # from filename
    else:
        if not filename:
            print("SKIP - missing filename")
            return

        if not os.path.isfile(filename):
            print(f"SKIP - not a file ({filename})")
            return

        # if not filename.endswith('.json'):
        #     print(f"SKIP - not a JSON file ({filename})")
        #     return

        with open(filename) as file:
            data = json.load(file)

    if type(data) is not list:
        print(f"SKIP - invalid JSON, not a LIST ({filename})")
        return

    return data


# ---------------------------------------------------------------------
# YAML File to DATA
# ---------------------------------------------------------------------
# from file, default from filename
# Second, try filename


def load_file_yaml(file=None, filename=None, pipeline_name=None, verbose=None):

    data = []

    # TODO : pipeline

    # from file
    if file:
        try:
            data = yaml.load(file, Loader=yaml.SafeLoader)
        except Exception as e:
            print("SKIP - YAML syntax error in file : ", e)
            return

    # from filename
    else:
        if not filename:
            print("SKIP - missing filename")
            return

        if not os.path.isfile(filename):
            print(f"SKIP - not a file ({filename})")
            return

        # if not (filename.endswith('.yml') or filename.endswith('.yaml')):
        #     print(f"SKIP - not a YAML file ({filename})")
        #     return

        with open(filename) as f:
            try:
                data = yaml.load(f, Loader=yaml.SafeLoader)
            except Exception as e:
                print(f"SKIP - YAML syntax error in {filename} : ", e)

    if type(data) is not list:
        print(f"SKIP - invalid YAML, not a LIST ({filename})")
        return

    return data
