package com.ankurm.structuredlogging;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.math.BigDecimal;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * logging.structured.format.console=ecs (set in application.properties)
 * makes Logback's console appender write one Elastic Common Schema JSON
 * object per line instead of the usual human-readable pattern, with no
 * JSON library on the classpath at all - `mvn dependency:tree` on this
 * module has nothing matching "jackson": Boot's structured logging
 * formatters write JSON themselves. So this test checks the real line
 * with plain String.contains() rather than parsing it, which is exactly
 * how little machinery the feature actually needs.
 */
@SpringBootTest(classes = StructuredLoggingDemoApplication.class)
class EcsStructuredLoggingTest {

    private static final Logger plainLog = LoggerFactory.getLogger(EcsStructuredLoggingTest.class);

    @Autowired
    OrderService orderService;

    @Test
    void placingAnOrderWritesOneEcsJsonLineWithTheFluentAndMdcFields() throws Exception {
        String orderLine = capture(() -> orderService.placeOrder("ord-101", "acme-corp", new BigDecimal("249.99")));

        assertThat(orderLine).startsWith("{").endsWith("}");
        assertThat(orderLine).contains("\"log\":{\"level\":\"INFO\"");
        assertThat(orderLine).contains("\"service\":{\"name\":\"structured-logging-demo\"");
        assertThat(orderLine).contains("\"message\":\"order placed\"");
        // SLF4J fluent addKeyValue() fields, flattened to top-level members:
        assertThat(orderLine).contains("\"orderId\":\"ord-101\"");
        assertThat(orderLine).contains("\"amount\":249.99");
        // MDC.put("tenant", ...) around the call, also flattened to top level:
        assertThat(orderLine).contains("\"tenant\":\"acme-corp\"");
        // logging.structured.json.add.corpname=ankurm - a fixed member on every line:
        assertThat(orderLine).contains("\"corpname\":\"ankurm\"");

        Transcript t = new Transcript("31-ecs-order-line.txt");
        t.line(orderLine);
        t.write();
    }

    @Test
    void mdcPutDoesNotLeakIntoALogLineWrittenAfterMdcRemove() throws Exception {
        orderService.placeOrder("ord-202", "globex", new BigDecimal("10.00"));
        String afterLine = capture(() -> plainLog.info("no tenant in scope here"));

        assertThat(afterLine).contains("\"message\":\"no tenant in scope here\"");
        assertThat(afterLine).doesNotContain("globex");
        assertThat(afterLine).doesNotContain("\"tenant\"");

        Transcript t = new Transcript("31-ecs-mdc-scoping.txt");
        t.line("after OrderService.placeOrder(..., \"globex\", ...) returns, MDC.remove(\"tenant\") has already run");
        t.line("next line logged outside that method has no tenant member:");
        t.line(afterLine);
        t.write();
    }

    private interface LogAction {
        void run();
    }

    private static String capture(LogAction action) throws Exception {
        PrintStream realOut = System.out;
        ByteArrayOutputStream captured = new ByteArrayOutputStream();
        System.setOut(new PrintStream(captured, true, "UTF-8"));
        try {
            action.run();
        } finally {
            System.setOut(realOut);
        }
        return captured.toString("UTF-8").strip();
    }
}
