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

"""
The RAW YAML/JSON Code Editor: prefills from an existing Schema or Instance ("Edit
as YAML" links) or starts blank, auto-detects YAML vs JSON on paste. Optionally runs
entries through a Pipeline, then either just verifies parsing or imports via
load_broker. Fully independent of views_importer.py - _import_and_report() below is
a deliberate duplicate of the one there, not a shared import, so the two modules
have no cross-dependency.

Method inventory: code_editor.
Private helpers: _parse_rawdata, _import_and_report.
"""

import json

import yaml
from django.contrib import messages
from django.shortcuts import redirect, render
from django.utils.translation import gettext as _

from app_data.aaa import start_view
from app_data.data import Instance
from app_data.loader import load_broker
from app_data.pipeline import Pipeline
from app_data.schema import Schema
from app_home.log import DEBUG, INFO, WARNING, log


# -------------------------------------------------------------------------
# RAW CODE EDITOR
# -------------------------------------------------------------------------
def code_editor(request, classname=None, id=None):
    """GET: prefill the editor with a schema's or instance's YAML, or start blank.
    POST: parse the textarea (auto-detecting YAML vs JSON), then either verify or
    import via load_broker (optionally through a Pipeline)."""

    context = start_view(
        request,
        app="data",
        view="code_editor",
        noauth="app_sirene:private",
        perm="p_code_editor",
        noauthz="app_home:private",
    )
    if context["redirect"]:
        return redirect(context["redirect"])
    aaa = context["aaa"]

    rawdata = ""

    # Get existing data or empty
    if request.method == "GET":
        # no id          : => schema from classname
        # id             : => instance

        # Schema
        if not id:
            schema = Schema.from_name(classname)
            try:
                rawdata = schema.to_yaml()
            except Exception:
                pass

        # Instance
        else:
            instance = Instance.from_id(id)
            if instance:
                rawdata = instance.to_yaml()

    elif request.method == "POST":
        rawdata = request.POST.get("rawdata", "")

        pipeline_name = request.POST.get("pipeline") or None
        pipeline = Pipeline.from_name(pipeline_name) if pipeline_name else None

        datalist, error = _parse_rawdata(rawdata)
        if error:
            messages.add_message(request, messages.ERROR, str(error))
            log(WARNING, aaa=aaa, app="data", view="code_editor",
                action="parse", status="KO", data=str(error))  # fmt: skip
        else:
            submit = request.POST.get("submit")

            if submit == "verify":
                # TODO / load with dryrun
                messages.add_message(request, messages.SUCCESS, _("Check ok"))
                log(DEBUG, aaa=aaa, app="data", view="code_editor", action="check", status="OK")

            elif submit == "import":
                _import_and_report(request, aaa, datalist, pipeline)
                return redirect(request.path)

    # edit
    context["rawdata"] = rawdata
    context["pipelines"] = Pipeline.list(is_enabled=True)
    return render(request, "app_data/code_editor.html", context)


def _parse_rawdata(rawdata):
    """Parse RAW content as JSON or YAML, auto-detecting the format: if it looks like
    JSON (starts with "{" or "["), try json.loads first for a precise error message;
    otherwise, and as a fallback if that fails, use yaml.safe_load (which also accepts
    valid JSON). Returns (datalist, None) - datalist is guaranteed to be a list - or
    (None, error), where error is either the caught parse exception or a plain
    message when parsing succeeded but the result isn't a list."""

    stripped = rawdata.lstrip()
    data = None
    parsed = False
    if stripped[:1] in ("{", "["):
        try:
            data = json.loads(rawdata)
            parsed = True
        except Exception:
            pass  # fall through to YAML

    if not parsed:
        try:
            data = yaml.safe_load(rawdata)
        except Exception as e:
            return None, e

    if type(data) is not list:
        return None, _("Failed: content is not a list")

    return data, None


def _import_and_report(request, aaa, datalist, pipeline):
    """Apply an optional Pipeline per entry then load via load_broker; report the
    ok/ko counts as a Django message. Deliberate duplicate of the same-named
    function in views_importer.py - see this module's docstring."""

    count_ok = 0
    count_ko = 0
    for datadict in datalist:
        if pipeline:
            status = pipeline.apply(datadict)
            if status == "discard":
                continue
        result = load_broker([datadict], aaa=aaa)
        if result.get("errors"):
            count_ko += result.get("count_ko", 0)
        else:
            count_ok += result.get("count_ok", 0)

    if count_ko:
        messages.add_message(request, messages.ERROR,
            _("Code editor: ") + f" (ok={count_ok}, ko={count_ko})")  # fmt: skip
        log(WARNING, aaa=aaa, app="data", view="code_editor",
            action="save", status="KO", data=f"ok={count_ok} ko={count_ko}")  # fmt: skip
    else:
        messages.add_message(request, messages.SUCCESS, _("Code editor OK") + f" ({count_ok})")
        log(INFO, aaa=aaa, app="data", view="code_editor",
            action="save", status="OK", data=f"{count_ok} objects")  # fmt: skip
