package com.scopely.infrastructure.kinesis;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.kinesis.AmazonKinesis;
import com.amazonaws.services.kinesis.AmazonKinesisClient;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.S3ObjectSummary;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class KinesisVcr {
    private static final Logger LOGGER = LoggerFactory.getLogger(KinesisVcr.class);

    public static void main(String[] args) {
        VcrConfiguration vcrConfiguration = new VcrConfiguration(System.getenv());
        vcrConfiguration.validateConfiguration();

        AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain();
        AmazonS3 s3 = new AmazonS3Client(credentialsProvider);
        AmazonKinesis kinesis = new AmazonKinesisClient(credentialsProvider);

        if (args.length > 0 && ("play".equals(args[0]) || "estimate".equals(args[0]))) {

            if (args.length == 1) {
                throw new IllegalArgumentException("Must be called with at least two arguments: e.g., `kinesis-vcr play 2014-05-01T00:00:00 2015-05-01T00:00:00` " +
                        "or `kinesis-vcr play 2014-05-01T00:00:00`");
            }

            String startDateArg = args[1];

            LocalDateTime start = parseToLocalDateTime(startDateArg);

            if (start == null) {
                throw new IllegalArgumentException("Could not parse start date; should be formatted 2015-08-01 or 2015-08-01T12:12:00");
            }

            LocalDateTime end = null;
            if (args.length > 2) {
                end = parseToLocalDateTime(args[2]);

                if (end == null) {
                    throw new IllegalArgumentException("Could not parse end date; should be formatted 2015-08-01 or 2015-08-01T12:12:00");
                }
            }

            KinesisPlayer player = new KinesisPlayer(vcrConfiguration, s3, kinesis);

            if ("play".equals(args[0])) {
                replay(player, start, end, vcrConfiguration);
            } else {
                estimateReplayTime(player, start, end, vcrConfiguration);
            }
        } else {
            KinesisRecorder recorder = new KinesisRecorder(vcrConfiguration, s3, credentialsProvider);
            recorder.run();
        }
    }

    private static void estimateReplayTime(KinesisPlayer player, LocalDateTime start, LocalDateTime end, VcrConfiguration vcrConfiguration) {
        AtomicLong fileCount = new AtomicLong();
        long dataSizeInBytes = player
                .playableObjects(start, end)
                .doOnNext(ignore -> fileCount.incrementAndGet())
                .map(S3ObjectSummary::getSize)
                .reduce((lhs, rhs) -> lhs + rhs)
                .toBlocking()
                .first();

        // number of shards of the target stream
        int numberOfShards = player.getNumberOfShards();

        // all replayable data size in MB
        long dataSizeInMegaBytes = dataSizeInBytes / 1000 / 1000;

        // each shard can receive up to 1MB/s
        long estimatedTimeInMinutes = dataSizeInMegaBytes / numberOfShards / 60;

        // ceil time to biggest (reasonable) time unit
        String estimatedTimeHuman = minsToHumanReadableTimeSpan(estimatedTimeInMinutes);

        LOGGER.info("Target stream ({}) has {} shards", vcrConfiguration.targetStream, numberOfShards);
        LOGGER.info("It would take around {} to replay the data in the provided range, which has {} files", estimatedTimeHuman, fileCount);
    }

    private static void replay(KinesisPlayer player, LocalDateTime start, LocalDateTime end, VcrConfiguration vcrConfiguration) {
        AtomicInteger recordsCounter = new AtomicInteger();
        int count = player
                .play(start, end)
                .doOnNext(each -> System.out.print("Sent " + recordsCounter.incrementAndGet() + " records to kinesis\r"))
                .count()
                .toBlocking()
                .first();

        LOGGER.info("Wrote {} records to output Kinesis stream {}", count, vcrConfiguration.targetStream);
        System.exit(0);
    }

    private static LocalDateTime parseToLocalDateTime(String input) {
        LocalDateTime dateTime = null;
        try {
            dateTime = LocalDateTime.parse(input);
        } catch (DateTimeParseException ignored) {
            // no-op
        }

        try {
            dateTime = LocalDate.parse(input).atTime(0, 0);
        } catch (DateTimeParseException ignored) {
            // no-op
        }

        return dateTime;
    }

    /**
     * Rounds up the provided time in minutes to the biggest time unit (up to months)
     */
    private static String minsToHumanReadableTimeSpan(long timeInMinutes) {
        if (timeInMinutes < 60) {
            return String.format(Locale.US, "%d mins", timeInMinutes);
        }

        if (TimeUnit.MINUTES.toHours(timeInMinutes) < 24) {
            return String.format(Locale.US, "%d hours", TimeUnit.MINUTES.toHours(timeInMinutes));
        }

        long timeInDays = TimeUnit.MINUTES.toDays(timeInMinutes);
        if (timeInDays < 30) {
            return String.format(Locale.US, "%d days", timeInDays);
        }

        return String.format(Locale.US, "%d months", timeInDays / 30);
    }
}

@@ -1355,16 +1355,200 @@
 %5D))) %7B%0A%0A
+            if (vcrConfiguration.targetStream == null) %7B%0A                throw new IllegalArgumentException(%22Must specify a target stream for playback or estimation.%22);%0A            %7D%0A%0A
         
@@ -4113,16 +4113,42 @@
 %7B%7D files
+ and a total size of %7B%7D MB
 %22, estim
@@ -4171,16 +4171,37 @@
 ileCount
+, dataSizeInMegaBytes
 );%0A    %7D

Include total MB in estimate