package ru.yandex.webmaster3.core.http;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * @author aherman
 */
public class RequestTrace {
    private int cassandraReadCount;
    private long totalCassandraReadTimeNano;

    private int cassandraBatchCount;
    private long totalCassandraBatchTimeNano;

    private int cassandraWriteCount;
    private long totalCassandraWriteTimeNano;

    private int clickhouseReadCount;
    private long totalClickhouseReadTimeNano;

    private int clickhouseWriteCount;
    private long totalClickhouseWriteTimeNano;

    private int ydbReadCount;
    private long totalYdbReadTimeNano;

    private int ydbWriteCount;
    private long totalYdbWriteTimeNano;

    private final ArrayList<Query> cassandraQueries = new ArrayList<>();
    private final ArrayList<Query> clickhouseQueries = new ArrayList<>();
    private final ArrayList<Query> ydbQueries = new ArrayList<>();

    private final long requestStartTimeNano;
    private long requestEndTimeNano;

    private final boolean traceQueries;

    public RequestTrace(long wallTimeNano, boolean traceQueries) {
        this.requestStartTimeNano = wallTimeNano;
        this.traceQueries = traceQueries;
    }

    public synchronized void endRequest(long wallTimeNano) {
        this.requestEndTimeNano = wallTimeNano;
    }

    public synchronized void stopRead(Db db, String query, long queryTimeNano) {
        if (db == Db.CASSANDRA) {
            cassandraReadCount++;
            totalCassandraReadTimeNano += queryTimeNano;
            if (traceQueries) {
                cassandraQueries.add(new Query(query, queryTimeNano));
            }
        } else if (db == Db.CLICKHOUSE) {
            clickhouseReadCount++;
            totalClickhouseReadTimeNano += queryTimeNano;
            if (traceQueries) {
                clickhouseQueries.add(new Query(query, queryTimeNano));
            }
        } else if (db == Db.YDB) {
            ydbReadCount++;
            totalYdbReadTimeNano += queryTimeNano;
            if (traceQueries) {
                ydbQueries.add(new Query(query, queryTimeNano));
            }
        }
    }

    public synchronized void stopWrite(Db db, String query, long queryTimeNano) {
        if (db == Db.CASSANDRA) {
            cassandraWriteCount++;
            totalCassandraWriteTimeNano += queryTimeNano;
            if (traceQueries) {
                cassandraQueries.add(new Query(query, queryTimeNano));
            }
        } else if (db == Db.CLICKHOUSE) {
            clickhouseWriteCount++;
            totalClickhouseWriteTimeNano += queryTimeNano;
        } else if (db == Db.YDB) {
            ydbWriteCount++;
            totalYdbWriteTimeNano += queryTimeNano;
            if (traceQueries) {
                ydbQueries.add(new Query(query, queryTimeNano));
            }
        }
    }

    public synchronized void stopBatch(Db db, long queryTimeNano) {
        if (db == Db.CASSANDRA) {
            cassandraBatchCount++;
            totalCassandraBatchTimeNano += queryTimeNano;
        }
    }

    public synchronized long getRequestDuration(TimeUnit unit) {
        return unit.convert(requestEndTimeNano - requestStartTimeNano, TimeUnit.NANOSECONDS);
    }

    public synchronized long getAllQueriesDuration(TimeUnit unit) {
        return unit.convert(totalCassandraReadTimeNano + totalCassandraWriteTimeNano + totalCassandraBatchTimeNano
                + totalClickhouseReadTimeNano + totalClickhouseWriteTimeNano, TimeUnit.NANOSECONDS);
    }

    @JsonIgnore
    public synchronized int getCassandraReadCount() {
        return cassandraReadCount;
    }

    @JsonIgnore
    public synchronized int getCassandraBatchCount() {
        return cassandraBatchCount;
    }

    @JsonIgnore
    public synchronized int getCassandraWriteCount() {
        return cassandraWriteCount;
    }

    @JsonIgnore
    public synchronized int getClickhouseReadCount() {
        return clickhouseReadCount;
    }

    @JsonIgnore
    public synchronized int getClickhouseWriteCount() {
        return clickhouseWriteCount;
    }

    @JsonIgnore
    public synchronized ArrayList<Query> getCassandraQueries() {
        return cassandraQueries;
    }

    @JsonIgnore
    public synchronized int getYdbReadCount() {
        return ydbReadCount;
    }

    @JsonIgnore
    public synchronized int getYdbWriteCount() {
        return ydbWriteCount;
    }

    @JsonIgnore
    public synchronized ArrayList<Query> getYdbQueries() {
        return ydbQueries;
    }

    public synchronized long getTotalCassandraReadTimeNano(TimeUnit unit) {
        return unit.convert(totalCassandraReadTimeNano, TimeUnit.NANOSECONDS);
    }

    public synchronized long getTotalCassandraBatchTimeNano(TimeUnit unit) {
        return unit.convert(totalCassandraBatchTimeNano, TimeUnit.NANOSECONDS);
    }

    public synchronized long getTotalCassandraWriteTimeNano(TimeUnit unit) {
        return unit.convert(totalCassandraWriteTimeNano, TimeUnit.NANOSECONDS);
    }

    public synchronized long getTotalClickhouseReadTimeNano(TimeUnit unit) {
        return unit.convert(totalClickhouseReadTimeNano, TimeUnit.NANOSECONDS);
    }

    public synchronized long getTotalClickhouseWriteTimeNano(TimeUnit unit) {
        return unit.convert(totalClickhouseWriteTimeNano, TimeUnit.NANOSECONDS);
    }

    public synchronized long getTotalYdbReadTimeNano(TimeUnit unit) {
        return unit.convert(totalYdbReadTimeNano, TimeUnit.NANOSECONDS);
    }

    public synchronized long getTotalYdbWriteTimeNano(TimeUnit unit) {
        return unit.convert(totalYdbWriteTimeNano, TimeUnit.NANOSECONDS);
    }

    // Json properties
    public synchronized long getTimeMs() {
        return getRequestDuration(TimeUnit.MILLISECONDS);
    }

    public synchronized long getQueryTimeMs() {
        return getAllQueriesDuration(TimeUnit.MILLISECONDS);
    }

    public synchronized int getCassRead() {
        return cassandraReadCount;
    }

    public synchronized long getCassReadMs() {
        return getTotalCassandraReadTimeNano(TimeUnit.MILLISECONDS);
    }

    public synchronized int getCassWrite() {
        return cassandraWriteCount;
    }

    public synchronized long getCassWriteMs() {
        return getTotalCassandraWriteTimeNano(TimeUnit.MILLISECONDS);
    }

    public synchronized int getCassBatch() {
        return cassandraWriteCount;
    }

    public synchronized long getBatchMs() {
        return getTotalCassandraWriteTimeNano(TimeUnit.MILLISECONDS);
    }

    public synchronized int getCHRead() {
        return clickhouseReadCount;
    }

    public synchronized long getCHReadMs() {
        return getTotalClickhouseReadTimeNano(TimeUnit.MILLISECONDS);
    }

    public synchronized int getCHWrite() {
        return clickhouseWriteCount;
    }

    public synchronized long getCHWriteMs() {
        return getTotalClickhouseWriteTimeNano(TimeUnit.MILLISECONDS);
    }

    public synchronized int getYRead() {
        return ydbReadCount;
    }

    public synchronized long getYReadMs() {
        return getTotalYdbReadTimeNano(TimeUnit.MILLISECONDS);
    }

    public synchronized int getYWrite() {
        return ydbWriteCount;
    }

    public synchronized long getYWriteMs() {
        return getTotalYdbWriteTimeNano(TimeUnit.MILLISECONDS);
    }

    public synchronized List<Query> getCassQs() {
        return cassandraQueries;
    }

    public synchronized List<Query> getChQs() {
        return clickhouseQueries;
    }

    public synchronized List<Query> getYQs() {
        return ydbQueries;
    }

    public static class Query {
        private final String query;
        private final long timeNano;

        public Query(String query, long timeNano) {
            this.query = query;
            this.timeNano = timeNano;
        }

        public String getQ() {
            return query;
        }

        public long getT() {
            return TimeUnit.NANOSECONDS.toMillis(timeNano);
        }
    }

    public enum Db {
        CASSANDRA,
        CLICKHOUSE,
        YDB
    }
}
