/*-
 * ~~~~~~licensing~~~~~~
 * cef-logger-layout
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.emdev.cef;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;

import java.time.ZonedDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;


public class CEFRecord {
    public static final String SPACE = " ";
    public static final String EQUALS = "=";
    private String type;
    private String performedBy;
    private String action;
    private String src;
    private String dst;
    private String shost;
    private String suid;
    private String suser;
    private String msg;
    private String cs1;
    private String cs1Label;
    private String severity;
    private final long end;
    private String outcome;
    private String duser;
    private String spriv;

    public CEFRecord() {
        this.end = ZonedDateTime.now().toInstant().toEpochMilli();
    }

    public String getSrc() {
        return src;
    }

    public CEFRecord setSrc(String src) {
        this.src = src;
        return this;
    }

    public String getDst() {
        return dst;
    }

    public CEFRecord setDst(String dst) {
        this.dst = dst;
        return this;
    }

    public String getShost() {
        return shost;
    }

    public CEFRecord setShost(String shost) {
        this.shost = shost;
        return this;
    }

    public String getSuid() {
        return suid;
    }

    public CEFRecord setSuid(String suid) {
        this.suid = suid;
        return this;
    }

    public String getSuser() {
        return suser;
    }

    public CEFRecord setSuser(String suser) {
        this.suser = suser;
        return this;
    }

    public String getMsg() {
        return msg;
    }

    public CEFRecord setMsg(String msg) {
        this.msg = msg;
        return this;
    }

    public String getCs1() {
        return cs1;
    }

    public CEFRecord setCs1(String cs1) {
        this.cs1 = cs1;
        return this;
    }

    public String getCs1Label() {
        return cs1Label;
    }

    public CEFRecord setCs1Label(String cs1Label) {
        this.cs1Label = cs1Label;
        return this;
    }

    public long getEnd() {
        return end;
    }

    public String getType() {
        return type;
    }

    public CEFRecord setType(String type) {
        this.type = type;
        return this;
    }

    public String getPerformedBy() {
        return performedBy;
    }

    public CEFRecord setPerformedBy(String performedBy) {
        this.performedBy = performedBy;
        return this;
    }

    public String getAction() {
        return action;
    }

    public CEFRecord setAction(String action) {
        this.action = action;
        return this;
    }

    public String getSeverity() {
        return severity;
    }

    public CEFRecord setSeverity(String severity) {
        this.severity = severity;
        return this;
    }

    public static CEFRecord fromJson(JsonNode jsonNode) {
        String type = safeGet(jsonNode, "typ").asText("");
        String action = safeGet(jsonNode, "action").asText("");
        String performedBy = safeGet(jsonNode, "performedBy").asText("");
        JsonNode info = safeGet(jsonNode, "info");
        String src = safeGet(info, "src").asText("");
        String shost = safeGet(info,"shost").asText("");
        String dst = safeGet(info, "dst").asText("");
        String suid = safeGet(info, "suid").asText("");
        String msg = safeGet(info, "msg").asText("");
        String suser = safeGet(info, "suser").asText("");
        String outcome = safeGet(info, "outcome").asText("");
        String severity = safeGet(info, "severity").asText("0");
        String duser = safeGet(info, "duser").asText("");
        String spriv = safeGet(info, "spriv").asText("");
        return new CEFRecord()
                .setType(type)
                .setAction(action)
                .setPerformedBy(performedBy)
                .setShost(shost)
                .setSrc(src)
                .setDst(dst)
                .setSuid(suid)
                .setMsg(msg)
                .setSuser(suser)
                .setSeverity(severity)
                .setOutcome(outcome)
                .setDuser(duser)
                .setSpriv(spriv);
    }

    private static JsonNode safeGet(JsonNode node, String path) {
        JsonNode child = node.get(path);
        if (child == null) {
            return NullNode.getInstance();
        } else {
            return child;
        }
    }

    public String asString(String header, String separator) {
        final StringBuilder builder = new StringBuilder(header);
        builder
                .append(separator).append(type) //eventClassId
                .append(separator).append(action) //name
                .append(separator).append(severity).append(separator);
        BiConsumer<String, Object> consumer = (key, value) -> builder.append(key).append(EQUALS).append(value).append(SPACE);
        Processor p = new Processor(consumer);
        p.process("src", src).
                process("dst", dst).
                process("shost", shost).
                process("suid", suid).
                process("suser", suser).
                process("duser", duser).
                process("spriv", spriv).
                process("msg", msg.replaceAll("=", "\\\\=").replaceAll("\\n", " ")).
                process("outcome", outcome).
                process("end", end);
        return builder.toString();
    }

    public Map<String, ?> asJson(Map<String, String> defaults) {
        final Map<String, Object> data = new LinkedHashMap<>(defaults);
        Processor p = new Processor(data::put);
        p.process("typ", type).
                process("action", action).
                process("severity", severity).
                process("src", src).
                process("dst", dst).
                process("shost", shost).
                process("suid", suid).
                process("suser", suser).
                process("duser", duser).
                process("spriv", spriv).
                process("msg", msg.replaceAll("\\n", " ")).
                process("outcome", outcome).
                process("cs1", cs1).
                process("cs1Label", cs1Label).
                process("end", end);
        return data;
    }

    private<T> void acceptNotEmpty(String key, T value, BiConsumer<String, T> consumer) {
        if (value != null && notEmpty(String.valueOf(value))) {
            consumer.accept(key, value);
        }
    }

    public CEFRecord setOutcome(String outcome) {
        this.outcome = outcome;
        return this;
    }

    public String getOutcome() {
        return outcome;
    }

    public CEFRecord setDuser(String duser) {
        this.duser = duser;
        return this;
    }

    public String getDuser() {
        return duser;
    }

    public CEFRecord setSpriv(String spriv) {
        this.spriv = spriv;
        return this;
    }

    public String getSpriv() {
        return spriv;
    }

    static class Processor {
        final BiConsumer<String, Object> consumer;
        Processor(BiConsumer<String, Object> consumer) {
            this.consumer = consumer;
        }

        public Processor process(String key, Object value) {
            acceptNotEmpty(key, value);
            return this;
        }

        private<T> void acceptNotEmpty(String key, T value) {
            if (value != null && notEmpty(String.valueOf(value))) {
                consumer.accept(key, value);
            }
        }

    }
    private static boolean notEmpty(String s) {
        return s != null && s.trim().length() > 0;
    }
}
