/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.dbms.diagnostics.profile;

import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;

import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Optional;
import jdk.jfr.consumer.RecordingFile;
import org.apache.commons.io.output.NullPrintStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.neo4j.configuration.BootloaderSettings;
import org.neo4j.configuration.Config;
import org.neo4j.configuration.GraphDatabaseSettings;
import org.neo4j.dbms.diagnostics.jmx.JMXDumper;
import org.neo4j.dbms.diagnostics.jmx.JmxDump;
import org.neo4j.io.fs.FileSystemAbstraction;
import org.neo4j.io.fs.FileSystemUtils;
import org.neo4j.memory.EmptyMemoryTracker;
import org.neo4j.test.extension.Inject;
import org.neo4j.test.extension.testdirectory.TestDirectoryExtension;
import org.neo4j.test.utils.TestDirectory;
import org.neo4j.time.Clocks;

@TestDirectoryExtension
@DisabledOnOs(OS.WINDOWS)
class JfrProfilerTest {
    @Inject
    TestDirectory dir;

    @Inject
    FileSystemAbstraction fs;

    private JfrProfiler profiler;

    @AfterEach
    void after() {
        if (profiler != null) {
            profiler.stop();
        }
    }

    @Test
    void shouldStartAndStopJfr() throws IOException {
        createProfiler();
        try {
            assertThat(profiler.hasRunningRecording()).isFalse();
            profiler.start();
            assertThat(profiler.hasRunningRecording()).isTrue();
            assertThat(getRecordingFiles()).hasSize(1);
        } finally {
            profiler.stop();
        }
        assertHasValidRecording();
    }

    @Test
    void shouldRunJfrInTool() throws IOException {
        createProfiler();
        try (ProfileTool tool = new ProfileTool()) {
            tool.add(profiler);
            tool.start();
            assertThat(getRecordingFiles()).hasSize(1);
        }
        assertThat(profiler.hasRunningRecording()).isFalse();
        assertHasValidRecording();
    }

    private void assertHasValidRecording() throws IOException {
        Path[] recordingFiles = getRecordingFiles();
        assertThat(recordingFiles).hasSize(1);
        assertThat(isValidRecording(recordingFiles[0])).isTrue();
    }

    private boolean isValidRecording(Path file) throws IOException {
        try (var recordingFile = new RecordingFile(file)) {
            int numEvents = 0;
            while (recordingFile.hasMoreEvents()) {
                recordingFile.readEvent();
                numEvents++;
            }
            recordingFile.readEventTypes();
            return numEvents > 0; // We have parsed the file and found at least one event
        }
    }

    private Path[] getRecordingFiles() throws IOException {
        return fs.listFiles(
                dir.homePath(), path -> path.getFileName().toString().endsWith(".jfr"));
    }

    private void createProfiler() throws IOException {
        assertThat(profiler).isNull();
        Path pidFile = dir.file("test.pid");
        Config cfg = Config.newBuilder()
                .set(GraphDatabaseSettings.neo4j_home, dir.homePath())
                .set(BootloaderSettings.pid_file, pidFile)
                .build();
        FileSystemUtils.writeString(
                fs, pidFile, format("%s%n", ProcessHandle.current().pid()), EmptyMemoryTracker.INSTANCE);
        JMXDumper jmxDumper =
                new JMXDumper(cfg, fs, NullPrintStream.NULL_PRINT_STREAM, NullPrintStream.NULL_PRINT_STREAM, true);
        Optional<JmxDump> maybeDump = jmxDumper.getJMXDump();
        assumeThat(maybeDump).isPresent(); // IF not, then no point in running tests
        JmxDump jmxDump = maybeDump.get();
        profiler = new JfrProfiler(jmxDump, fs, dir.homePath(), Duration.ofMinutes(1), Clocks.nanoClock());
    }
}
