/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.atlas.notification.spool;

import org.apache.atlas.notification.spool.models.IndexRecord;
import org.apache.atlas.notification.spool.models.IndexRecords;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

public class IndexManagementTest extends BaseTest {
    @Test
    public void fileNameGeneration() {
        String             handlerName = "someHandler";
        SpoolConfiguration cfg         = getSpoolConfiguration(spoolDir, handlerName);
        IndexRecord        record      = new IndexRecord(StringUtils.EMPTY);

        assertEquals(SpoolUtils.getIndexFileName(cfg.getSourceName(), cfg.getMessageHandlerName()), "index-test-src-someHandler.json");
        assertTrue(SpoolUtils.getSpoolFileName(cfg.getSourceName(), cfg.getMessageHandlerName(), record.getId()).startsWith("spool-test-src-someHandler-"));
    }

    @Test
    public void verifyLoad() throws IOException {
        final int          expectedRecords = 2;
        SpoolConfiguration cfg             = getSpoolConfiguration();

        IndexManagement.IndexFileManager indexFileManager = new IndexManagement.IndexFileManager(SOURCE_TEST, cfg.getIndexFile(), cfg.getIndexDoneFile(), null, 2);

        assertEquals(indexFileManager.getRecords().size(), expectedRecords);

        assertEquals(indexFileManager.getRecords().get(0).getId(), "1");
        assertEquals(indexFileManager.getRecords().get(1).getId(), "2");
    }

    @Test
    public void addAndRemove() throws IOException {
        File newIndexFile     = getNewIndexFile('3');
        File newIndexDoneFile = getNewIndexDoneFile('3');

        IndexManagement.IndexFileManager indexFileManager = new IndexManagement.IndexFileManager(SOURCE_TEST, newIndexFile, newIndexDoneFile, null, 2);

        int expectedCount = 2;

        assertEquals(indexFileManager.getRecords().size(), expectedCount);

        IndexRecord r3 = indexFileManager.add("3.log");
        IndexRecord r4 = indexFileManager.add("4.log");
        IndexRecord r5 = indexFileManager.add("5.log");

        r4.updateFailedAttempt();
        indexFileManager.updateIndex(r4);

        r5.setLine(100);
        indexFileManager.updateIndex(r5);

        IndexRecords records = indexFileManager.loadRecords(newIndexFile);
        assertTrue(records.getRecords().containsKey(r3.getId()));
        assertTrue(records.getRecords().containsKey(r4.getId()));
        assertTrue(records.getRecords().containsKey(r5.getId()));

        assertEquals(records.getRecords().get(r3.getId()).getStatus(), r3.getStatus());
        assertEquals(records.getRecords().get(r4.getId()).getFailedAttempt(), r4.getFailedAttempt());
        assertEquals(records.getRecords().get(r5.getId()).getLine(), r5.getLine());

        indexFileManager.remove(r3);
        indexFileManager.remove(r4);
        indexFileManager.remove(r5);

        assertEquals(indexFileManager.getRecords().size(), expectedCount);
    }

    @Test
    public void verifyOperations() throws IOException {
        SpoolConfiguration cfg = getSpoolConfigurationTest();

        File newIndexFile     = getNewIndexFile('2');
        File newIndexDoneFile = getNewIndexDoneFile('2');

        File                             archiveDir       = cfg.getArchiveDir();
        IndexManagement.IndexFileManager indexFileManager = new IndexManagement.IndexFileManager(SOURCE_TEST, newIndexFile, newIndexDoneFile, null, 2);

        verifyAdding(indexFileManager);
        verifySaveAndLoad(indexFileManager);
        verifyRemove(indexFileManager);
        verifyRecords(indexFileManager);

        checkDoneFile(newIndexDoneFile, archiveDir, 2, "5.log");

        verifyArchiving(indexFileManager);
    }

    @AfterClass
    public void tearDown() {
        FileUtils.deleteQuietly(new File(spoolDirTest));
    }

    private void verifyRecords(IndexManagement.IndexFileManager indexFileManager) {
        List<IndexRecord> records = indexFileManager.getRecords();

        assertEquals(records.size(), 5);
        assertTrue(records.get(3).getPath().endsWith("3.log"));
        assertEquals(records.get(3).getStatus(), IndexRecord.STATUS_WRITE_IN_PROGRESS);
        assertEquals(records.get(2).getFailedAttempt(), 0);
        assertEquals(records.get(1).getDoneCompleted(), 0);
        assertEquals(records.get(0).getLine(), 0);
        assertFalse(records.get(0).getLastSuccess() != 0);
    }

    private void verifyAdding(IndexManagement.IndexFileManager indexFileManager) throws IOException {
        addFile(indexFileManager, spoolDirTest, "2.log");
        addFile(indexFileManager, spoolDirTest, "3.log");
        addFile(indexFileManager, spoolDirTest, "4.log");
        addFile(indexFileManager, spoolDirTest, "5.log");
    }

    private void verifyArchiving(IndexManagement.IndexFileManager indexFileManager) {
        indexFileManager.remove(indexFileManager.getRecords().get(1));
        indexFileManager.remove(indexFileManager.getRecords().get(1));
        indexFileManager.remove(indexFileManager.getRecords().get(1));
        indexFileManager.remove(indexFileManager.getRecords().get(1));

        checkArchiveDir(archiveDir);
    }

    private void verifyRemove(IndexManagement.IndexFileManager indexFileManager) throws IOException {
        indexFileManager.remove(indexFileManager.getRecords().get(5));

        boolean isPending = indexFileManager.getRecords().size() > 0;

        assertTrue(isPending);
    }

    private void verifySaveAndLoad(IndexManagement.IndexFileManager indexFileManager) throws IOException {
        indexFileManager.getRecords().get(2).updateFailedAttempt();
        indexFileManager.getRecords().get(3).setDone();
        indexFileManager.getRecords().get(1).setDoneCompleted(333L);
        indexFileManager.getRecords().get(0).setCurrentLine(999);

        assertEquals(indexFileManager.getRecords().size(), 6);
    }

    private void checkArchiveDir(File archiveDir) {
        Set<String> availableFiles = new HashSet<>();

        availableFiles.add(new File(archiveDir, "3.log").toString());
        availableFiles.add(new File(archiveDir, "4.log").toString());

        if (!archiveDir.exists()) {
            return;
        }

        File[] files = archiveDir.listFiles();

        assertNotNull(files);
        assertEquals(files.length, 1);
    }

    private void addFile(IndexManagement.IndexFileManager indexFileManager, String dir, String fileName) throws IOException {
        File file = new File(dir, fileName);

        file.createNewFile();

        indexFileManager.add(file.toString());
    }

    private void checkDoneFile(File newIndexDoneFile, File archiveDir, int maxArchiveFiles, String expectedFilePath) throws IOException {
        IndexManagement.IndexFileManager indexFileManager = new IndexManagement.IndexFileManager(SOURCE_TEST, newIndexDoneFile, newIndexDoneFile, null, maxArchiveFiles);

        assertEquals(indexFileManager.getRecords().size(), 2);
        assertTrue(indexFileManager.getRecords().get(1).getPath().endsWith(expectedFilePath));
    }
}
