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

import re

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

from app_data.data import Instance
from app_data.models import DataEAV


# ---------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------
def get_dataview_dict(schemaname):
    """
    get available dataviews for schema
    use DataEAV cache

    IN:  schema name (string
    OUT: dict { keyname:displayname , ... }
    """
    reply = {}

    eav_entries = DataEAV.objects.filter(
        classname="_dataview", fieldname="target_class", value=schemaname
    ).values("keyname", "displayname")

    # <QuerySet [{'keyname': 'aaaaa', 'displayname': 'bbbb'},  ... ]

    for adict in eav_entries:
        try:
            k = adict["keyname"]
            v = adict["displayname"]
            reply[k] = v
        except Exception:
            pass

    return reply


def get_dataview_content_by_name(dataview_name):

    reply = None

    instance = Instance.from_keyname(classname="_dataview", keyname=dataview_name)
    if not instance:
        return

    content = {}
    try:
        content = instance.fields["content"].value[0]
        reply = yaml.safe_load(content)
    except Exception as e:
        print(f"ERR - can't access dataview content ({dataview_name}): {e}")
        return

    return reply


def dataview_for_request(request=None, classname=None, update_session=False):
    """
    get requested dataview (request, session, default)
    provide dataview object
    update session

    return: dataview or None
    """
    if not classname:
        return

    if not request:
        return

    # DATAVIEW
    # --------
    # Transform to a DATAVIEW structure (subset/superset of columns, widgets, ...)
    # 1. get available dataviews (default, global, per user)
    # 2. get requested dataview (or default dataview)
    # 3. loop over instances / extract columns / build structure

    # 1. Get Dataview :  request > session > single > CLASS_default > DEFAULT built-in
    dataview_name = None
    dv_found = False
    dataview_default = classname + "_default"

    dataview_selector = get_dataview_dict(classname)

    #  request
    dataview_name = request.GET.get("dv", None)
    if not dataview_name:
        dataview_name = request.POST.get("dv", None)

    if dataview_name:
        if dataview_name == dataview_default:
            dv_found = True
        else:
            m = re.compile(r"[a-zA-Z0-9()_/.-]*$")
            if m.match(dataview_name):
                if dataview_name in dataview_selector:
                    dv_found = True

    #  in session ?
    if not dv_found:
        try:
            dataview_name = request.session["dataviews"][classname]
            if dataview_name:
                m = re.compile(r"[a-zA-Z0-9()_/.-]*$")
                if m.match(dataview_name):
                    if dataview_name in dataview_selector:
                        dv_found = True
        except Exception:
            pass

    # single entry in selector ?
    if not dv_found:
        if len(dataview_selector) == 1:
            # dataview_name = dataview_selector[0]
            dataview_name = next(iter(dataview_selector))
            dv_found = True

    # CLASSNAME_default
    if not dv_found:
        if dataview_default in dataview_selector:
            dataview_name = dataview_default
            dv_found = True

    #  get or built-in default
    if dv_found:
        dataview = DataView(keyname=dataview_name)
    else:
        dataview = DataView()

    if dataview.target_class != classname:
        # wrong class, use a default view
        dataview = DataView()

    #  store dataview in user session
    if update_session:
        if dataview_name:
            if "dataviews" not in request.session:
                request.session["dataviews"] = {classname: dataview_name}
                # request.session.modified = True
            else:
                request.session["dataviews"][classname] = dataview_name
                request.session.modified = True

    # store request specifics in dataview object
    if dataview_default not in dataview_selector:
        dataview.default = dataview_default
    # dataview.selector = {}
    dataview.selector = dataview_selector
    # for k,v in dataview_selector.items():
    #     #dataview.selector[k] = v.displayname
    #     dataview.selector[k] = v

    return dataview


# -------------------------------------------
# DataView CLASS
# -------------------------------------------


class DataView:
    def __init__(self, keyname=None):

        self.iobj = None
        self.target_class = None
        self.keyname = keyname
        self.displayname = _("Default View")
        self.is_enabled = True
        self.content = {"columns": ["keyname", "displayname", "last_update"]}
        self.columns = ["keyname", "displayname", "last_update"]

        # request specifics
        self.default = None  # name of default if not in selector
        self.selector = None  # list of available dataviews for target_class

        if not keyname:
            return

        # query _dataview Schema for keyname
        instance = Instance.from_keyname(classname="_dataview", keyname=keyname)
        if not instance:
            return
        if not instance.is_bound:
            return

        self.iobj = instance.iobj
        try:
            self.target_class = instance.fields["target_class"].value[0]
        except Exception:
            self.target_class = None
        self.displayname = instance.displayname
        self.is_enabled = instance.is_enabled
        try:
            raw = instance.fields["content"].value[0]
        except Exception:
            raw = ""

        try:
            self.content = yaml.safe_load(raw)
        except Exception as e:
            print(f"ERR - can't parse dataview content ({keyname}): {e}")
            return

        # Extract columns
        # columns:
        #   - nice name:
        #       from: keyname
        #   - col1
        #    - site:
        #        from: mysitename_col2
        #
        yaml_columns = self.content.get("columns", None)
        computed_columns = []
        if yaml_columns:
            for head in yaml_columns:
                if type(head) is str:
                    computed_columns.append(head)
                elif type(head) is dict:
                    for col, nouse in head.items():
                        computed_columns.append(col)
        self.columns = computed_columns

        # NEXT: if no keyname/displayname , add keyname ?

    def get_columns(self):

        reply = []
        raw = self.content.get("columns")
        for entry in raw:
            if type(entry) is str:
                reply.append(entry)
            elif type(entry) is dict:
                reply.append(list(entry.keys())[0])
            else:
                reply.append("")
        return reply

    def filter(self, instance=None, mode="datapoint", value_only=False):
        """

        IN:
          * dataview() object, instance() object
          * mode: datapoint|value|csv

        OUT:
          * array ["val1","val2", {}, {},{} ...]

        values:
            * string if keyname/displayname/...
            * mode=datapoint : datapoint dict {}
            * mode=value
                {'fieldname': 'mystring',
                'displayname': 'MyString',
                'description': 'A string',
                'dataformat': 'string',
                'dataformat_ext': None,
                'is_multi': False,
                'bigset': False,
                'value':  'data for test1-01'
                        {} for more complex fields like enumerate, user
                }
            * mode=csv : value or val1^val2^val3 ... if multi
        """

        if not instance:
            return

        data = []

        # get dataview requested columns
        columns_array = self.content.get("columns")
        if not columns_array:
            return

        for head in columns_array:
            # no operator, no rename, direct column name
            if type(head) is str:
                if head == "keyname":
                    data.append(instance.keyname)
                elif head == "displayname":
                    data.append(instance.displayname)
                elif head == "is_enabled":
                    if instance.is_enabled:
                        data.append("X")
                    else:
                        data.append("")
                elif head == "last_update":
                    data.append(instance.last_update)
                else:
                    # existing column
                    if head in instance.fields:
                        cell = ""
                        if mode == "value":
                            cell = instance.fields[head].get_value()
                        elif mode == "datapoint":
                            cell = instance.fields[head].get_datapoint_ui_detail()
                        elif mode == "csv":
                            cell = instance.fields[head].get_csv_cell()
                        data.append(cell)

                    # computed column
                    else:
                        # Strange : non-existent column but no compute primitive ; add anyway (empty)
                        data.append("")

            # operator, rename, ...
            # - newcol:              <= head is dict (col==head)
            #     from: old_col      <= operator_line = operator: operator_value

            elif type(head) is dict:
                cell = ""

                #  apply all operators
                for col, operator_line in head.items():
                    if operator_line:
                        for operator, operator_value in operator_line.items():
                            if operator == "from":
                                if operator_value in instance.fields:
                                    if mode == "value":
                                        cell = instance.fields[operator_value].get_value()
                                    elif mode == "datapoint":
                                        cell = instance.fields[
                                            operator_value
                                        ].get_datapoint_ui_detail()
                                    elif mode == "csv":
                                        cell = instance.fields[operator_value].get_csv_cell()
                                elif operator_value == "displayname":
                                    cell = instance.displayname

                                elif operator_value == "keyname":
                                    cell = instance.keyname

                                elif operator_value == "last_update":
                                    cell = instance.last_update

                data.append(cell)

            else:
                data.append("")

        return data

    def print(self):
        print("_dataview")
        print(f"    keyname:        {self.keyname}")
        print(f"    iobj:           {self.iobj}")
        print(f"    target_class:   {self.target_class}")
        print(f"    is_enabled:     {self.is_enabled}")
        print(f"    displayname:    {self.displayname}")
        print(f"    columns:        {self.columns}")
        print(f"    content:        {self.content}")
