package org.railwaystations.rsapi.adapter.out.photostorage;

import org.apache.commons.io.IOUtils;
import org.railwaystations.rsapi.core.model.Country;
import org.railwaystations.rsapi.core.model.InboxEntry;
import org.railwaystations.rsapi.core.model.Station;
import org.railwaystations.rsapi.core.ports.out.PhotoStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

@Repository
public class PhotoFileStorage implements PhotoStorage {

    private static final Logger LOG = LoggerFactory.getLogger(PhotoFileStorage.class);

    private static final long MAX_SIZE = 20_000_000L;

    private final WorkDir workDir;

    public PhotoFileStorage(final WorkDir workDir) {
        this.workDir = workDir;
    }

    @Override
    public boolean isProcessed(final String filename) {
        return filename != null && Files.exists(workDir.getInboxProcessedDir().resolve(filename));
    }

    @Override
    public void importPhoto(final InboxEntry inboxEntry, final Country country, final Station station) throws IOException {
        final Path originalFile = getUploadFile(inboxEntry.getFilename());
        final Path processedFile = workDir.getInboxProcessedDir().resolve(inboxEntry.getFilename());
        final Path destinationFile = workDir.getPhotosDir().resolve(country.getCode()).resolve(sanitizeFilename(station.getKey().getId() + "." + inboxEntry.getExtension()));
        if (Files.exists(processedFile)) {
            Files.move(processedFile, destinationFile, REPLACE_EXISTING);
        } else {
            Files.copy(originalFile, destinationFile, REPLACE_EXISTING);
        }
        Files.move(originalFile, workDir.getInboxDoneDir().resolve(originalFile.getFileName()), REPLACE_EXISTING);
    }

    @Override
    public void reject(final InboxEntry inboxEntry) throws IOException {
        final Path file = getUploadFile(inboxEntry.getFilename());
        Files.move(file, workDir.getInboxRejectedDir().resolve(file.getFileName()), REPLACE_EXISTING);
        Files.deleteIfExists(workDir.getInboxToProcessDir().resolve(inboxEntry.getFilename()));
        Files.deleteIfExists(workDir.getInboxProcessedDir().resolve(inboxEntry.getFilename()));
    }

    @Override
    public Long storeUpload(final InputStream body, final String filename) throws PhotoStorage.PhotoTooLargeException, IOException {
        final Path file = getUploadFile(filename);
        LOG.info("Writing photo to {}", file);

        // write the file to the inbox directory
        final CheckedOutputStream cos = new CheckedOutputStream(Files.newOutputStream(file), new CRC32());
        final long bytesRead = IOUtils.copyLarge(body, cos, 0L, MAX_SIZE);
        if (bytesRead == MAX_SIZE) {
            Files.deleteIfExists(file);
            throw new PhotoStorage.PhotoTooLargeException(MAX_SIZE);
        }
        cos.close();

        // additionally write the file to the input directory for Vsion.AI
        Files.copy(file, workDir.getInboxToProcessDir().resolve(file.getFileName()), REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
        return cos.getChecksum().getValue();
    }

    @Override
    public Path getUploadFile(final String filename) {
        return workDir.getInboxDir().resolve(filename);
    }

    @Override
    public Path getPhotoFile(final String countryCode, final String filename) {
        return workDir.getPhotosDir().resolve(sanitizeFilename(countryCode)).resolve(sanitizeFilename(filename));
    }

    @Override
    public Path getInboxFile(final String filename) {
        return workDir.getInboxDir().resolve(sanitizeFilename(filename));
    }

    @Override
    public Path getInboxProcessedFile(final String filename) {
        return workDir.getInboxProcessedDir().resolve(sanitizeFilename(filename));
    }

    @Override
    public void cleanupOldCopies() {
        final Instant maxAge = Instant.now().minus(workDir.getKeepFileCopiesInDays(), ChronoUnit.DAYS);
        cleanupOldCopiesFrom(workDir.getInboxDoneDir(), maxAge);
        cleanupOldCopiesFrom(workDir.getInboxRejectedDir(), maxAge);
    }

    static void cleanupOldCopiesFrom(final Path dir, final Instant maxAge) {
        try {
            Files.list(dir)
                    .filter(Files::isRegularFile)
                    .filter(f -> isOlderThan(f, maxAge))
                    .forEach(PhotoFileStorage::deleteSilently);
        } catch (final Exception e) {
            LOG.error("Failed to cleanup old copies from {}", dir, e);
        }
    }

    static void deleteSilently(final Path path) {
        try {
            Files.delete(path);
            LOG.info("Deleted {}", path);
        } catch (final IOException e) {
            LOG.warn("Unable to delete {}", path);
        }
    }

    static boolean isOlderThan(final Path path, final Instant maxAge) {
        try {
            return Files.getLastModifiedTime(path).toInstant().isBefore(maxAge);
        } catch (final IOException e) {
            LOG.warn("Unable to getLastModifiedTime of {}", path.getFileName());
        }
        return false;
    }

    static String sanitizeFilename(final String fileName) {
        if (fileName == null) {
            return null;
        }

        return fileName.replace(" ", "_")
                .replace("/", "_")
                .replace(":", "_")
                .replace("\"", "_")
                .replace("|", "_")
                .replace("*", "_")
                .replace("?", "_")
                .replace("<", "_")
                .replace(">", "_")
                .replace('\\', '_');
    }

}

@@ -1449,21 +1449,20 @@
 nal 
-Path original
+var uploaded
 File
@@ -1519,20 +1519,19 @@
   final 
-Path
+var
  process
@@ -1619,20 +1619,19 @@
   final 
-Path
+var
  destina
@@ -1935,24 +1935,24 @@
 es.copy(
-original
+uploaded
 File, de
@@ -1987,32 +1987,50 @@
 ING);%0A        %7D%0A
+        try %7B%0A    
         Files.mo
@@ -2032,24 +2032,24 @@
 es.move(
-original
+uploaded
 File, wo
@@ -2084,16 +2084,16 @@
 lve(
-original
+uploaded
 File
@@ -2124,24 +2124,157 @@
 _EXISTING);%0A
+        %7D catch (final Exception e) %7B%0A            LOG.warn(%22Couldn't move original file %7B%7D to done dir%22, uploadedFile, e);%0A        %7D%0A
     %7D%0A%0A    @
@@ -2361,36 +2361,35 @@
 %7B%0A        final 
-Path
+var
  file = getUploa
@@ -2884,20 +2884,19 @@
   final 
-Path
+var
  file = 
@@ -3031,35 +3031,19 @@
   final 
-CheckedOutputStream
+var
  cos = n
@@ -3122,20 +3122,19 @@
   final 
-long
+var
  bytesRe
@@ -4351,31 +4351,27 @@
       final 
-Instant
+var
  maxAge = In

Move uploaded file to done dir is only a warning