/*-
 * ~~~~~~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 java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

@Plugin(name = "CEFLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public class CEFLayout extends AbstractStringLayout {
    public static final String EMDEV_EVENTS = "isEmdevEvents";
    private final String header;

    private static final ObjectMapper mapper = new ObjectMapper();
    private final String separator;

    private final String format;
    private final Map<String, String> defaults;
    private AuditLayout auditLayout;

    public CEFLayout(String version, String separator, String vendor, String product,
            String productVersion, String format) {
        super(Charset.defaultCharset());
        this.separator = separator;
        this.format = format;
        header = new StringBuilder()
                .append("CEF:").append(version).append(separator)
                .append(vendor).append(separator)
                .append(product).append(separator)
                .append(productVersion).toString();
        String pattern =
                "CEF:${" + AuditLayout.CEF_VERSION + "}|${" + AuditLayout.VENDOR + "}|${" + AuditLayout.PRODUCT + "}|${"
                        + AuditLayout.VERSION + "}|${"
                        + AuditLayout.EVENT_TYPE + "}|${" + AuditLayout.EVENT_NAME + "}|${" + AuditLayout.SEVERITY
                        + "}|${" + AuditLayout.EXTENSION + "}";
        this.auditLayout = new AuditLayout(pattern);
        Map<String, String> overrides = new HashMap<>();
        overrides.put(AuditLayout.CEF_VERSION, version);
        overrides.put(AuditLayout.VENDOR, vendor);
        overrides.put(AuditLayout.PRODUCT, product);
        overrides.put(AuditLayout.VERSION, productVersion);
        this.defaults = new LinkedHashMap<>(overrides);
        auditLayout.addOverrides(overrides);
    }

    // test-only
    void setAuditLayout(AuditLayout layout) {
        this.auditLayout = layout;
    }

    @Override
    public String toSerializable(LogEvent event) {
        String formattedMessage = event.getMessage().getFormattedMessage();
        String result = null;
        if (formattedMessage != null) {
            try {
                result = tryProcessEmdevEvent(formattedMessage);
            } catch (IOException e) {

            }
            if (result == null) {
                result = tryAlternate(event);
            }
        }
        return result;
    }

    private String tryAlternate(LogEvent event) {
        String result = null;
        if (format.equalsIgnoreCase("json")) {
            Map<String, String> parsedMessage = auditLayout.jsonMap(event);
            try {
                result = mapper.writeValueAsString(parsedMessage) + "\n";
            } catch (JsonProcessingException e) {
                // pass
            }
        } else {
            result = auditLayout.toSerializable(event);
        }
        return result;
    }

    private String tryProcessEmdevEvent(String formattedMessage) throws IOException {
        String result = null;
        JsonNode jsonNode = mapper.readTree(formattedMessage);
        JsonNode isEmdev = jsonNode.get(EMDEV_EVENTS);
        boolean process = isEmdev != null && !isEmdev.isNull() && isEmdev.asBoolean(false);
        if (process) {
            CEFRecord record = CEFRecord.fromJson(jsonNode);
            if (record.getSrc() != null) {
                String resolvedAddress = DnsResolver.tryResolve(record.getSrc());
                if (resolvedAddress != null) {
                    record.setSrc(resolvedAddress);
                }
            }
            if (this.format.equalsIgnoreCase("cef")) {
                result = record.asString(header, separator) + "\n";
            } else if (format.equalsIgnoreCase("json")) {
                Map<String, ?> jsonRecord = record.asJson(defaults);
                result = mapper.writeValueAsString(jsonRecord) + "\n";
            }
        }
        return result;
    }

    @PluginFactory
    public static CEFLayout createCEFLayout(
            @PluginAttribute(value = "version", defaultString = "0") String version,
            @PluginAttribute(value = "separator", defaultString = "|") String separator,
            @PluginAttribute(value = "vendor", defaultString = "EMDEV") String vendor,
            @PluginAttribute(value = "product") String product,
            @PluginAttribute(value = "productVersion") String productVersion,
            @PluginAttribute(value = "format", defaultString = "cef") String format) {
        return new CEFLayout(version, separator, vendor, product, productVersion, format);
    }
}
