package org.bdware.sc.db;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.sc.index.TimeSerialIndex;
import org.bdware.sc.util.JsonUtil;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class TimeRocksDBUtil {
    private static final Logger LOGGER = LogManager.getLogger(TimeRocksDBUtil.class);
    private final Map<String, RocksDB> dbLink;
    private final Map<String, TimeSerialIndex> index;
    public String dbPath;
    Random random = new Random();

    public TimeRocksDBUtil(String path) {
        dbLink = new HashMap<>();
        index = new HashMap<>();
        dbPath = path;
    }

    private RocksDB getDB(String dbName) {
        if (!dbLink.containsKey(dbName)) {
            setupDB(dbName);
        }
        return dbLink.get(dbName);
    }

    private void setupDB(String dbName) {
        Options options = new Options();
        options.setCreateIfMissing(true);
        File file = new File(dbPath + "/" + dbName);
        String path = file.getAbsolutePath();
        File lockFile = new File(path, "LOCK");
        lockFile.delete();
        RocksDB rocksDB = null;
        try {
            rocksDB = RocksDB.open(options, path);
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        File timeIndex = new File(dbPath + "/" + dbName + ".timeindex");
        dbLink.put(dbName, rocksDB);
        index.put(dbName, new TimeSerialIndex(timeIndex.getAbsolutePath()));
    }

    public void put(String dbName, String val) {
        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long key = random.nextLong();
        index.index(key);
        try {
            db.put(
                    String.valueOf(key).getBytes(StandardCharsets.UTF_8),
                    val.getBytes(StandardCharsets.UTF_8));
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }

    public void reIndex(String dbName, String date, String val) {
        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long key = random.nextLong();
        index.manullyIndex(Long.parseLong(date), key);
        try {
            db.put(
                    String.valueOf(key).getBytes(StandardCharsets.UTF_8),
                    val.getBytes(StandardCharsets.UTF_8));
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }


    public JsonArray countInIntreval(String dbName, long start, long interval, long end) {
        TimeSerialIndex index = getIndex(dbName);
        long offset = index.findNearest(start);
        JsonArray array = new JsonArray();
        do {
            start += interval;
            long offset2 = index.findNearest(start);
            array.add(offset2 - offset);
            offset = offset2;
        } while (start < end);
        return array;
    }

    public List<String> query(String dbName, long end) {
        List<String> ret = new ArrayList<>();
        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);
        for (Long l : data) {
            try {
                String t = new String(db.get(l.toString().getBytes(StandardCharsets.UTF_8)));
                if (!t.isEmpty()) {
                    ret.add(t);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public String queryDetail(String dbName, String key) {
        //        if (!dbName.equals(CMTables.LocalContractLogDB.toString())) {
        //            return null;
        //        }
        try {
            RocksDB db = getDB(dbName);
            return new String(db.get(key.getBytes(StandardCharsets.UTF_8)));
        } catch (RocksDBException e) {
            e.printStackTrace();
        }

        return null;
    }

    public List<JsonObject> queryLogBefore(String dbName, long end, String contractName) {
        if (!dbName.equals(CMTables.LocalContractLogDB.toString())) {
            return null;
        }
        List<JsonObject> ret = new ArrayList<>();
        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);
        for (Long l : data) {
            try {
                String t = new String(db.get(l.toString().getBytes(StandardCharsets.UTF_8)));
                if (null != t && !t.isEmpty()) {
                    JsonObject jo = JsonUtil.parseStringAsJsonObject(t);
                    jo.addProperty("key", l.toString());
                    if (jo.has("contractName")
                            && jo.get("contractName").getAsString().equals(contractName)) {
                        ret.add(jo);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public List<String> queryWithKey(String dbName, long end) {
        if (!dbName.equals(CMTables.LocalContractLogDB.toString())) {
            return null;
        }

        List<String> ret = new ArrayList<>();
        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        for (Long l : data) {
            try {
                String t = new String(db.get(l.toString().getBytes(StandardCharsets.UTF_8)));
                if (t != null && !t.isEmpty()) {
                    t = t.substring(0, t.length() - 1);
                    t += (",\"key\":\"" + l + "\"}");
                    ret.add(t);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public List<String> querySort(String dbName, long end) {
        if (!(dbName.equals("NC_CMLog")
                || dbName.equals("NC_NodeLogDB")
                || dbName.equals(CMTables.LocalNodeLogDB.toString())
                || dbName.equals(CMTables.LocalContractLogDB.toString()))) {
            return null;
        }

        List<String> ret = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        Map<String, String> data2;

        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        String action;
        for (Long l : data) {
            try {
                String t = new String(db.get(l.toString().getBytes(StandardCharsets.UTF_8)));
                data2 = JsonUtil.fromJson(t, new TypeToken<Map<String, String>>() {
                }.getType());
                if (data2 != null) {
                    action = data2.get("action");
                    if (map.containsKey(action)) {
                        int count = map.get(action) + 1;
                        map.put(action, count);
                    } else {
                        map.put(action, 1);
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        for (String key : map.keySet()) {
            ret.add("{\"action\":\"" + key + "\",\"times\":" + map.get(key) + "}");
        }

        return ret;
    }

    public List<String> queryDate(String dbName, long end) {
        if (!dbName.equals("NC_CMLog")
                && !dbName.equals("NC_NodeLogDB")
                && !dbName.equals(CMTables.LocalNodeLogDB.toString())
                && !dbName.equals(CMTables.LocalContractLogDB.toString())) {
            return null;
        }

        List<String> ret = new ArrayList<>();
        Map<String, String> data2;

        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        long size = index.size();
        long offset = index.findNearest(end);
        int len = (int) (size - offset);
        List<Long> data = index.request(offset, len);

        String date;
        for (Long l : data) {
            try {
                data2 =
                        JsonUtil.fromJson(
                                new String(db.get(l.toString().getBytes(StandardCharsets.UTF_8))),
                                new TypeToken<Map<String, String>>() {
                                }.getType());
                if (data2 != null) {
                    date = data2.get("date");
                    ret.add(date);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public List<String> queryByOffset(String dbName, int offset, int len) {
        List<String> ret = new ArrayList<>();
        RocksDB db = getDB(dbName);
        TimeSerialIndex index = getIndex(dbName);
        List<Long> data = index.request(offset, len);
        for (Long l : data) {
            try {
                String t = new String(db.get(l.toString().getBytes(StandardCharsets.UTF_8)));
                ret.add(t);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ret;
    }

    public String get(String dbName, String key) {
        RocksDB db = getDB(dbName);
        try {
            return new String(db.get(key.toString().getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private TimeSerialIndex getIndex(String dbName) {
        return index.get(dbName);
    }

    public long getCount(String dbName) {
        try {
            TimeSerialIndex index = getIndex(dbName);
            return index.size();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0L;
    }
}
