/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.nifi.processors.flume;


import org.apache.flume.sink.NullSink;
import org.apache.flume.source.AvroSource;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.MockProcessContext;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.apache.nifi.util.file.FileUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ExecuteFlumeSourceTest {

    private static final Logger logger = LoggerFactory.getLogger(ExecuteFlumeSourceTest.class);

    @Test
    public void testValidators() {
        TestRunner runner = TestRunners.newTestRunner(ExecuteFlumeSource.class);
        Collection<ValidationResult> results;
        ProcessContext pc;

        results = new HashSet<>();
        runner.enqueue(new byte[0]);
        pc = runner.getProcessContext();
        if (pc instanceof MockProcessContext) {
            results = ((MockProcessContext) pc).validate();
        }
        assertEquals(1, results.size());
        for (ValidationResult vr : results) {
            logger.debug(vr.toString());
            assertTrue(vr.toString().contains("is invalid because Source Type is required"));
        }

        // non-existent class
        results = new HashSet<>();
        runner.setProperty(ExecuteFlumeSource.SOURCE_TYPE, "invalid.class.name");
        runner.enqueue(new byte[0]);
        pc = runner.getProcessContext();
        if (pc instanceof MockProcessContext) {
            results = ((MockProcessContext) pc).validate();
        }
        assertEquals(1, results.size());
        for (ValidationResult vr : results) {
            logger.debug(vr.toString());
            assertTrue(vr.toString().contains("is invalid because unable to load source"));
        }

        // class doesn't implement Source
        results = new HashSet<>();
        runner.setProperty(ExecuteFlumeSource.SOURCE_TYPE, NullSink.class.getName());
        runner.enqueue(new byte[0]);
        pc = runner.getProcessContext();
        if (pc instanceof MockProcessContext) {
            results = ((MockProcessContext) pc).validate();
        }
        assertEquals(1, results.size());
        for (ValidationResult vr : results) {
            logger.debug(vr.toString());
            assertTrue(vr.toString().contains("is invalid because unable to create source"));
        }

        results = new HashSet<>();
        runner.setProperty(ExecuteFlumeSource.SOURCE_TYPE, AvroSource.class.getName());
        runner.enqueue(new byte[0]);
        pc = runner.getProcessContext();
        if (pc instanceof MockProcessContext) {
            results = ((MockProcessContext) pc).validate();
        }
        assertEquals(0, results.size());
    }

    @Test
    public void testSequenceSource() {
        TestRunner runner = TestRunners.newTestRunner(ExecuteFlumeSource.class);
        runner.setProperty(ExecuteFlumeSource.SOURCE_TYPE, "seq");
        runner.run();
        List<MockFlowFile> flowFiles = runner.getFlowFilesForRelationship(ExecuteFlumeSource.SUCCESS);
        assertEquals(1, flowFiles.size());
        for (MockFlowFile flowFile : flowFiles) {
            logger.debug(flowFile.toString());
            assertEquals(1, flowFile.getSize());
        }
    }

    @Test
    @Disabled("Does not work on Windows")
    public void testSourceWithConfig(@TempDir Path temp) throws IOException {
        File spoolDirectory = temp.resolve("spooldir").toFile();
        spoolDirectory.mkdirs();
        File dst = new File(spoolDirectory, "records.txt");
        FileUtils.copyFile(getClass().getResourceAsStream("/testdata/records.txt"), dst, true, false);

        TestRunner runner = TestRunners.newTestRunner(ExecuteFlumeSource.class);
        runner.setProperty(ExecuteFlumeSource.SOURCE_TYPE, "spooldir");
        runner.setProperty(ExecuteFlumeSink.FLUME_CONFIG,
            "tier1.sources.src-1.spoolDir = " + spoolDirectory.getAbsolutePath());
        runner.run(1, false, true);
        // Because the spool directory source is an event driven source, it may take some time for flow files to get
        // produced. I'm willing to wait up to 5 seconds, but will bail out early if possible. If it takes longer than
        // that then there is likely a bug.
        int numWaits = 10;
        while (runner.getFlowFilesForRelationship(ExecuteFlumeSource.SUCCESS).size() < 4 && --numWaits > 0) {
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException ex) {
                logger.warn("Sleep interrupted");
            }
        }
        runner.shutdown();
        runner.assertTransferCount(ExecuteFlumeSource.SUCCESS, 4);
        int i = 1;
        for (MockFlowFile flowFile : runner.getFlowFilesForRelationship(ExecuteFlumeSource.SUCCESS)) {
            flowFile.assertContentEquals("record " + i);
            i++;
        }
    }
}
