/*
 * 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.paimon.manifest;

import org.apache.paimon.CoreOptions;
import org.apache.paimon.data.BinaryRow;
import org.apache.paimon.data.Timestamp;
import org.apache.paimon.io.DataFileMeta;
import org.apache.paimon.operation.ManifestFileMerger;
import org.apache.paimon.options.Options;
import org.apache.paimon.stats.StatsTestUtils;
import org.apache.paimon.types.RowType;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

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

/** Test {@link ManifestFile}. for table without partition */
public class NoPartitionManifestFileMetaTest extends ManifestFileMetaTestBase {
    private final RowType noPartitionType = RowType.of();

    @TempDir java.nio.file.Path tempDir;
    private ManifestFile manifestFile;

    @BeforeEach
    public void beforeEach() {
        manifestFile = createManifestFile(tempDir.toString());
    }

    @Test
    public void testMerge() {
        List<ManifestFileMeta> input = createBaseManifestFileMetas(false);
        addDeltaManifests(input, false);

        Options testOptions = new Options();
        testOptions.set("manifest.target-file-size", "500B");
        testOptions.set("manifest.merge-min-count", "3");
        testOptions.set("manifest.full-compaction-threshold-size", "200B");
        List<ManifestFileMeta> merged =
                ManifestFileMerger.merge(
                        input,
                        manifestFile,
                        getPartitionType(),
                        CoreOptions.fromMap(testOptions.toMap()));
        assertEquivalentEntries(input, merged);

        // the first one is not deleted, it should not be merged
        assertThat(merged.get(0)).isSameAs(input.get(0));
    }

    @Test
    public void testMergeFullCompactionWithoutDeleteFile() {
        // entries are All ADD.
        List<ManifestFileMeta> input = new ArrayList<>();
        // base
        for (int j = 0; j < 6; j++) {
            List<ManifestEntry> entrys = new ArrayList<>();
            for (int i = 1; i < 50; i++) {
                entrys.add(makeEntry(true, String.format(manifestFileNameTemplate, j, i), null));
            }
            input.add(makeManifest(entrys.toArray(new ManifestEntry[0])));
        }
        // The base file all meet the manifest file size.
        long threshold = input.stream().mapToLong(ManifestFileMeta::fileSize).min().getAsLong();
        Set<String> baseFiles =
                input.stream().map(ManifestFileMeta::fileName).collect(Collectors.toSet());

        // assert base manifest are not accessed
        for (String baseFile : baseFiles) {
            manifestFile.delete(baseFile);
        }

        // delta
        input.add(makeManifest(makeEntry(true, "A", null)));
        input.add(makeManifest(makeEntry(true, "B", null)));
        input.add(makeManifest(makeEntry(true, "C", null)));
        input.add(makeManifest(makeEntry(true, "D", null)));
        input.add(makeManifest(makeEntry(true, "E", null)));
        input.add(makeManifest(makeEntry(true, "F", null)));
        input.add(makeManifest(makeEntry(true, "G", null)));

        Options testOptions = new Options();
        testOptions.set("manifest.target-file-size", threshold + "B");
        testOptions.set("manifest.merge-min-count", "3");
        testOptions.set("manifest.full-compaction-threshold-size", "200B");
        List<ManifestFileMeta> merged =
                ManifestFileMerger.merge(
                        input,
                        manifestFile,
                        getPartitionType(),
                        CoreOptions.fromMap(testOptions.toMap()));
        assertEquivalentEntries(
                input.stream()
                        .filter(f -> !baseFiles.contains(f.fileName()))
                        .collect(Collectors.toList()),
                merged.stream()
                        .filter(f -> !baseFiles.contains(f.fileName()))
                        .collect(Collectors.toList()));
    }

    @Test
    public void testDataEvolutionManifestSortByRowId() {
        List<ManifestFileMeta> input = new ArrayList<>();
        input.add(makeManifest(makeRowIdEntry("row20", 20, 5, 0), makeRowIdEntry("row0", 0, 5, 0)));
        input.add(
                makeManifest(
                        makeRowIdEntry("row10-seq1", 10, 5, 1),
                        makeRowIdEntry("row10-seq3", 10, 5, 3)));

        Options testOptions = new Options();
        testOptions.set("manifest-sort.enabled", "true");
        testOptions.set("row-tracking.enabled", "true");
        testOptions.set("data-evolution.enabled", "true");

        List<ManifestFileMeta> merged =
                ManifestFileMerger.merge(
                        input,
                        manifestFile,
                        getPartitionType(),
                        CoreOptions.fromMap(testOptions.toMap()));

        assertEquivalentEntries(input, merged);

        List<String> outputFileNames = new ArrayList<>();
        for (ManifestFileMeta meta : merged) {
            for (ManifestEntry entry : manifestFile.read(meta.fileName(), meta.fileSize())) {
                outputFileNames.add(entry.file().fileName());
            }
        }
        assertThat(outputFileNames).containsExactly("row0", "row10-seq3", "row10-seq1", "row20");
    }

    @Override
    public ManifestFile getManifestFile() {
        return manifestFile;
    }

    @Override
    public RowType getPartitionType() {
        return noPartitionType;
    }

    private ManifestEntry makeRowIdEntry(
            String fileName, long firstRowId, long rowCount, long sequenceNumber) {
        return ManifestEntry.create(
                FileKind.ADD,
                BinaryRow.EMPTY_ROW,
                0,
                0,
                DataFileMeta.create(
                        fileName,
                        0,
                        rowCount,
                        BinaryRow.EMPTY_ROW,
                        BinaryRow.EMPTY_ROW,
                        StatsTestUtils.newEmptySimpleStats(),
                        StatsTestUtils.newEmptySimpleStats(),
                        sequenceNumber,
                        sequenceNumber,
                        0,
                        0,
                        Collections.emptyList(),
                        Timestamp.fromEpochMillis(200000),
                        0L,
                        null,
                        FileSource.APPEND,
                        null,
                        null,
                        firstRowId,
                        Collections.singletonList("f0")));
    }
}
