# (c) cavaliba.com - cmt - api.py

"""
/api/cmt/: protocol used by remote CMT agents to fetch their config (GET) and
submit check results (POST). The calling agent's identity is the 3rd token of
the X-Cavaliba-Key header (see app_data.api.aaa.start_api's aaa["identity"]),
resolved against _cmtagent.

Method inventory: build_default_config, store_results, cmt.
"""

import json

import yaml
from django.views.decorators.csrf import csrf_exempt

import app_data.api.helper as helper
from app_cmt.common import get_check_names
from app_cmt.models import CMTRaw
from app_data.api.aaa import start_api
from app_data.data import Instance
from app_home.log import DEBUG, ERROR, log


def build_default_config(agent):
    """Return the DEFAULT-mode config dict for `agent`: its own conf field
    (parsed YAML, possibly empty/partial - may hold global/outputs sections)
    with "checks" overwritten by the generated aggregation of every enabled
    _cmtcheck (module + parsed conf, keyed by _cmtcheck UUID keyname)."""

    try:
        base = yaml.safe_load(agent.get_attribute_first("conf")) or {}
    except Exception:
        base = {}
    if not isinstance(base, dict):
        base = {}

    checks = {}
    for instance in Instance.list_classname(classname="_cmtcheck", enabled="yes"):
        try:
            conf = yaml.safe_load(instance.get_attribute_first("conf")) or {}
        except Exception:
            conf = {}
        if not isinstance(conf, dict):
            conf = {}
        entry = {"module": instance.get_attribute_first("module")}
        entry.update(conf)
        checks[instance.keyname] = entry

    base["checks"] = checks
    return base


def store_results(results):
    """
    Store submitted check results (list of dicts) into CMTRaw.

    Args:
        results: list of {"checkname": <_cmtcheck UUID>, "status": bool,
                 "usec": int, "error": str}

    Output:
        error : None or string with error message
    """

    if not isinstance(results, list):
        return "bad response"

    check_names = get_check_names()

    for item in results:
        checkname = item.get("checkname", None)
        if checkname not in check_names:
            return f"invalid checkname {checkname}"

        obj = CMTRaw()
        obj.checkname = checkname
        obj.status = bool(item.get("status"))
        if not obj.status:
            obj.data = item.get("error", "") or ""
        obj.usec = int(item.get("usec", 0) or 0)
        obj.save()

    return None


#   ----------------------------------------------------------------------------
# GET /api/cmt/ ; POST /api/cmt/
#   ----------------------------------------------------------------------------
@csrf_exempt
def cmt(request):
    """GET: return the calling agent's config (DEFAULT or CONF mode, per its
    _cmtagent.mode). POST: store submitted check results (via store_results)
    into CMTRaw."""

    if request.method not in ("GET", "POST"):
        log(ERROR, app="api/cmt", view="cmt", action=request.method,
            status="KO", data="method not allowed")  # fmt: skip
        return helper.send_error("method not allowed", 405)

    permission = "p_cmt_api_worklist" if request.method == "GET" else "p_cmt_api_result"

    aaa_api = start_api(request, permission=permission)
    if not aaa_api["is_allowed"]:
        log(ERROR, aaa=aaa_api, app="api/cmt", view="cmt", action=request.method,
            status="KO", data="denied")  # fmt: skip
        return helper.send_denied(aaa_api["error"])

    agent = Instance.from_keyname(classname="_cmtagent", keyname=aaa_api["identity"])
    if not agent or not agent.is_enabled:
        log(ERROR, aaa=aaa_api, app="api/cmt", view="cmt", action=request.method,
            status="KO", data=f"unknown agent identity '{aaa_api['identity']}'")  # fmt: skip
        return helper.send_error("unknown agent identity", 400)

    if request.method == "GET":
        mode = agent.get_attribute_first("mode")

        if mode == "CONF":
            try:
                reply = yaml.safe_load(agent.get_attribute_first("conf"))
            except Exception as e:
                log(ERROR, aaa=aaa_api, app="api/cmt", view="cmt", action="GET",
                    status="KO", data=f"invalid agent conf YAML: {e}")  # fmt: skip
                return helper.send_error(f"invalid agent conf YAML: {e}", 400)
            if not isinstance(reply, dict):
                reply = {}
        else:
            reply = build_default_config(agent)

        log(DEBUG, aaa=aaa_api, app="api/cmt", view="cmt", action="GET", status="OK", data="")
        return helper.send_response(request, reply, 200)

    # POST : submitted results
    try:
        datalist = json.loads(request.body.decode("utf-8"))
    except Exception as e:
        log(ERROR, aaa=aaa_api, app="api/cmt", view="cmt", action=request.method,
            status="KO", data=f"invalid JSON body: {e}")  # fmt: skip
        return helper.send_error(str(e), 400)

    results = datalist.get("results") if isinstance(datalist, dict) else None
    if results is None:
        log(ERROR, aaa=aaa_api, app="api/cmt", view="cmt", action=request.method,
            status="KO", data="bad json response")  # fmt: skip
        return helper.send_error("bad json response", 400)

    err = store_results(results)
    if err:
        log(ERROR, aaa=aaa_api, app="api/cmt", view="cmt", action=request.method,
            status="KO", data=err)  # fmt: skip
        return helper.send_error(err, 400)

    reply = {"status": "OK"}
    log(DEBUG, aaa=aaa_api, app="api/cmt", view="cmt", action="POST", status="OK", data="")
    return helper.send_response(request, reply, 200)
