/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) since 2016 Lightbend Inc. <https://www.lightbend.com>
 */

package docs.javadsl;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import org.apache.pekko.NotUsed;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.ClassicActorSystemProvider;
import org.apache.pekko.stream.connectors.file.TarArchiveMetadata;
import org.apache.pekko.stream.connectors.file.javadsl.Archive;
import org.apache.pekko.stream.connectors.file.javadsl.Directory;
import org.apache.pekko.stream.connectors.testkit.javadsl.LogCapturingExtension;
import org.apache.pekko.stream.javadsl.*;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.apache.pekko.util.ByteString;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ExtendWith(LogCapturingExtension.class)
public class NestedTarReaderTest {
  private static final Logger logger = LoggerFactory.getLogger(NestedTarReaderTest.class);

  private static final String TARGZ_EXT = "tar.gz";
  private static final int MAX_GUNZIP_CHUNK_SIZE = 64000;

  private static ActorSystem system;

  @BeforeAll
  public static void beforeAll() throws Exception {
    system = ActorSystem.create();
  }

  @AfterAll
  public static void afterAll() throws Exception {
    TestKit.shutdownActorSystem(system);
  }

  @Test
  public void flowShouldCreateZIPArchive() throws Exception {
    Path tempDir = Files.createTempDirectory("pekko-connectors-ftp");
    tempDir.toFile().deleteOnExit();
    Path file = Paths.get("./file/src/test/resources/nested-sample.tar");
    logger.info(
        "extracting {} into {}",
        file.toAbsolutePath().toString(),
        tempDir.toAbsolutePath().toString());
    List<TarArchiveMetadata> metadata =
        process(file, tempDir, system).toCompletableFuture().get(1, TimeUnit.MINUTES);
    List<String> names = metadata.stream().map(md -> md.filePathName()).toList();
    assertThat(names.size(), is(1281));
  }

  public static CompletionStage<List<TarArchiveMetadata>> process(
      Path filename, Path targetDir, ClassicActorSystemProvider system) {
    return FileIO.fromPath(filename).via(unTarFlow(targetDir, system)).runWith(Sink.seq(), system);
  }

  private static Flow<ByteString, TarArchiveMetadata, NotUsed> unTarFlow(
      Path targetDir, ClassicActorSystemProvider system) {
    return Archive.tarReader()
        .mapAsync(
            1,
            pair -> {
              TarArchiveMetadata metadata = pair.first();
              Source<ByteString, NotUsed> source = pair.second();
              Path targetFile = targetDir.resolve(metadata.filePath());
              CompletionStage<List<TarArchiveMetadata>> readMetadata;
              if (metadata.isDirectory()) {
                readMetadata =
                    Source.single(targetFile)
                        .via(Directory.mkdirs())
                        .toMat(Sink.ignore(), Keep.right())
                        .run(system)
                        .thenApply(d -> List.of(metadata));
              } else if (targetFile.getFileName().toString().endsWith(TARGZ_EXT)) {
                Path targetSubDir =
                    targetFile
                        .getParent()
                        .resolve(
                            Paths.get(
                                targetFile
                                    .getFileName()
                                    .toString()
                                    .substring(0, TARGZ_EXT.length() - 2)));
                readMetadata =
                    source
                        .via(Compression.gzipDecompress(MAX_GUNZIP_CHUNK_SIZE))
                        .via(unTarFlow(targetSubDir, system))
                        .runWith(Sink.seq(), system);
              } else {
                readMetadata =
                    source
                        .toMat(Sink.ignore(), Keep.right())
                        .run(system)
                        .thenApply(d -> List.of(metadata));
              }
              return readMetadata;
            })
        .flatMapConcat(Source::from);
  }
}
