package uk.co.automatictester.lightning.core.state.data;

import com.univocity.parsers.common.processor.ConcurrentRowProcessor;
import com.univocity.parsers.common.processor.RowListProcessor;
import com.univocity.parsers.csv.CsvParserSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.automatictester.lightning.core.exceptions.CSVFileNonexistentLabelException;
import uk.co.automatictester.lightning.core.s3client.factory.S3ClientFlyweightFactory;
import uk.co.automatictester.lightning.core.state.data.base.AbstractCsvEntries;

import java.io.File;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.regex.Pattern;

import static java.lang.Math.min;
import static java.util.Comparator.reverseOrder;
import static java.util.stream.Collectors.toList;
import static uk.co.automatictester.lightning.core.enums.JmeterColumns.*;

public class JmeterTransactions extends AbstractCsvEntries {

    private static final int MAX_NUMBER_OF_LONGEST_TRANSACTIONS = 5;
    private static final Logger log = LoggerFactory.getLogger(JmeterTransactions.class);

    private JmeterTransactions(File csvFile) {
        Instant start = Instant.now();
        log.debug("Reading CSV file - start");

        loadFromFile(csvFile);
        throwExceptionIfEmpty();

        Instant finish = Instant.now();
        Duration duration = Duration.between(start, finish);
        log.debug("Reading CSV file - finish, read {} rows, took {}ms", entries.size(), duration.toMillis());
    }

    private JmeterTransactions(String region, String bucket, String key) {
        s3Client = S3ClientFlyweightFactory.getInstance(region).setBucket(bucket);
        Instant start = Instant.now();
        log.debug("Reading CSV file - start");

        loadFromS3Object(key);
        throwExceptionIfEmpty();

        Instant finish = Instant.now();
        Duration duration = Duration.between(start, finish);
        log.debug("Reading CSV file - finish, read {} rows, took {}ms", entries.size(), duration.toMillis());
    }

    private JmeterTransactions(List<String[]> entries) {
        super(entries);
    }

    public static JmeterTransactions fromFile(File csvFile) {
        return new JmeterTransactions(csvFile);
    }

    public static JmeterTransactions fromS3Object(String region, String bucket, String key) {
        return new JmeterTransactions(region, bucket, key);
    }

    public static JmeterTransactions fromList(List<String[]> entries) {
        return new JmeterTransactions(entries);
    }

    public JmeterTransactions getTransactionsWith(String expectedTransactionName) {
        List<String[]> transactions = new ArrayList<>();
        entries.forEach(transaction -> {
            String transactionName = transaction[TRANSACTION_LABEL.getColumn()];
            boolean isInScope = transactionName.equals(expectedTransactionName);
            if (isInScope) {
                transactions.add(transaction);
            }
        });
        if (transactions.size() == 0) {
            throw new CSVFileNonexistentLabelException(expectedTransactionName);
        }
        return JmeterTransactions.fromList(transactions);
    }

    public JmeterTransactions getTransactionsMatching(String expectedTransactionName) {
        Pattern pattern = Pattern.compile(expectedTransactionName);
        List<String[]> transactions = new ArrayList<>();
        entries.forEach(transaction -> {
            String transactionName = transaction[TRANSACTION_LABEL.getColumn()];
            boolean isInScope = pattern.matcher(transactionName).matches();
            if (isInScope) {
                transactions.add(transaction);
            }
        });
        if (transactions.size() == 0) {
            throw new CSVFileNonexistentLabelException(expectedTransactionName);
        }
        return JmeterTransactions.fromList(transactions);
    }

    public List<Integer> getLongestTransactions() {
        int numberOfLongestTransactions = min(entries.size(), MAX_NUMBER_OF_LONGEST_TRANSACTIONS);
        return entries.stream()
                .map(e -> Integer.parseInt(e[TRANSACTION_DURATION.getColumn()]))
                .sorted(reverseOrder())
                .limit(numberOfLongestTransactions)
                .collect(toList());
    }

    public int getFailCount() {
        return (int) entries.stream()
                .filter(t -> "false".equals(t[TRANSACTION_RESULT.getColumn()]))
                .count();
    }

    public long getFirstTransactionTimestamp() {
        BiPredicate<Long, Long> isBefore = (edgeTimestamp, currentTimestamp) -> edgeTimestamp == 0 || currentTimestamp < edgeTimestamp;
        return getEdgeTransactionTimestamp(isBefore);
    }

    public long getLastTransactionTimestamp() {
        BiPredicate<Long, Long> isAfter = (edgeTimestamp, currentTimestamp) -> edgeTimestamp == 0 || currentTimestamp > edgeTimestamp;
        return getEdgeTransactionTimestamp(isAfter);
    }

    public String getJmeterReport() {
        return String.format("Transactions executed: %d, failed: %d", entries.size(), getFailCount());
    }

    @Override
    protected CsvParserSettings getCsvParserSettings() {
        CsvParserSettings parserSettings = new CsvParserSettings();
        parserSettings.setLineSeparatorDetectionEnabled(true);
        parserSettings.setHeaderExtractionEnabled(true);
        parserSettings.selectFields("label", "elapsed", "success", "timeStamp");
        RowListProcessor rowProcessor = new RowListProcessor();
        ConcurrentRowProcessor concurrentRowProcessor = new ConcurrentRowProcessor(rowProcessor);
        parserSettings.setProcessor(concurrentRowProcessor);
        return parserSettings;
    }

    private long getEdgeTransactionTimestamp(BiPredicate<Long, Long> biPredicate) {
        long edgeTimestamp = 0;
        for (String[] transaction : entries) {
            long currentTransactionTimestamp = Long.parseLong(transaction[TRANSACTION_TIMESTAMP.getColumn()]);
            boolean isEdgeTimestamp = biPredicate.test(edgeTimestamp, currentTransactionTimestamp);
            if (isEdgeTimestamp) {
                edgeTimestamp = currentTransactionTimestamp;
            }
        }
        return edgeTimestamp;
    }
}

@@ -680,47 +680,8 @@
 st;%0A
-import java.util.function.BiPredicate;%0A
 impo
@@ -4570,430 +4570,483 @@
     
-BiPredicate%3CLong, Long%3E isBefore = (edgeTimestamp, currentTimestamp) -%3E edgeTimestamp == 0 %7C%7C currentTimestamp %3C edgeTimestamp;%0A        return getEdgeTransactionTimestamp(isBefore);%0A    %7D%0A%0A    public long getLastTransactionTimestamp() %7B%0A        BiPredicate%3CLong, Long%3E isAfter = (edgeTimestamp, currentTimestamp) -%3E edgeTimestamp == 0 %7C%7C currentTimestamp %3E edgeTimestamp;%0A        return getEdgeTransactionTimestamp(isAfter
+return entries.stream()%0A                .mapToLong(e -%3E Long.parseLong(e%5BTRANSACTION_TIMESTAMP.getColumn()%5D))%0A                .sorted()%0A                .limit(1)%0A                .sum();%0A    %7D%0A%0A    public long getLastTransactionTimestamp() %7B%0A        return entries.stream()%0A                .map(e -%3E Long.parseLong(e%5BTRANSACTION_TIMESTAMP.getColumn()%5D))%0A                .sorted(reverseOrder())%0A                .limit(1)%0A                .mapToLong(e -%3E e)%0A                .sum(
 );%0A 
@@ -5802,538 +5802,7 @@
    %7D
-%0A%0A    private long getEdgeTransactionTimestamp(BiPredicate%3CLong, Long%3E biPredicate) %7B%0A        long edgeTimestamp = 0;%0A        for (String%5B%5D transaction : entries) %7B%0A            long currentTransactionTimestamp = Long.parseLong(transaction%5BTRANSACTION_TIMESTAMP.getColumn()%5D);%0A            boolean isEdgeTimestamp = biPredicate.test(edgeTimestamp, currentTransactionTimestamp);%0A            if (isEdgeTimestamp) %7B%0A                edgeTimestamp = currentTransactionTimestamp;%0A            %7D%0A        %7D%0A        return edgeTimestamp;%0A    %7D
 %0A%7D%0A

Simplify some logic, which should never get that complex in the first place