package com.iqser.red.service.persistence.management.v1.processor.dataexchange.zipreaders;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.UUID;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;

import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.utils.FileUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ZipEntryIterator implements Iterator<ZipEntryData>, AutoCloseable {

    private final static int THRESHOLD_ENTRIES = 10000;
    private final static int THRESHOLD_SIZE_DEFAULT = 1000000000; // 1GB
    private final long sizeThreshold;

    private final ZipArchiveInputStream zis;
    private final File tempFile;
    private final double compressionThresholdRatio;
    private int totalSizeArchive;
    private int totalEntryArchive;
    private ZipArchiveEntry nextEntry;


    public ZipEntryIterator(byte[] archive, double compressionThresholdRatio, long sizeThreshold) throws IOException {

        this.sizeThreshold = sizeThreshold;
        tempFile = FileUtils.createTempFile(archive, UUID.randomUUID().toString(), ".zip");
        this.zis = new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(tempFile)), "UTF8", true, true);
        this.compressionThresholdRatio = compressionThresholdRatio;
        this.nextEntry = zis.getNextZipEntry();
    }


    public ZipEntryIterator(byte[] archive, double compressionThresholdRatio) throws IOException {

        this(archive, compressionThresholdRatio, THRESHOLD_SIZE_DEFAULT);
    }


    @Override
    public boolean hasNext() {

        return nextEntry != null;
    }


    @Override
    public ZipEntryData next() {

        if (nextEntry == null) {
            throw new NoSuchElementException();
        }
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int nBytes;
            int totalSizeEntry = 0;

            while ((nBytes = zis.read(buffer)) > 0) {
                bos.write(buffer, 0, nBytes);
                totalSizeEntry += nBytes;
                totalSizeArchive += nBytes;

                double compressionRatio = (double) totalSizeEntry / nextEntry.getCompressedSize();
                if (compressionRatio > compressionThresholdRatio) {
                    throw new BadRequestException("ZIP-Bomb detected (compressionRatio). " + compressionRatio + "/" + compressionThresholdRatio);
                }
            }

            if (totalSizeArchive > sizeThreshold) {
                throw new BadRequestException("ZIP-Bomb detected. (threshold size)");
            }

            if (totalEntryArchive > THRESHOLD_ENTRIES) {
                throw new BadRequestException("ZIP-Bomb detected (threshold entries).");
            }

            byte[] bytes = bos.toByteArray();
            bos.close();
            ZipEntryData zipEntryData = new ZipEntryData(nextEntry, bytes);

            nextEntry = zis.getNextZipEntry();
            totalEntryArchive++;

            return zipEntryData;

        } catch (IOException e) {
            throw new BadRequestException(e.getMessage(), e);
        }
    }


    @Override
    public void close() throws IOException {

        zis.close();
        if (tempFile != null) {
            boolean isDeleted = tempFile.delete();
            if (!isDeleted) {
                log.debug("tempFile could not be deleted");
            }
        }
    }

}
