package io.wdd.common.utils;

import org.apache.commons.lang3.ObjectUtils;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class TimeUtils {

    private static final ZoneId SYSTEM_TIME_ZONE_ID = ZoneId.of("UTC+8");


    /**
     * https://memorynotfound.com/calculate-relative-time-time-ago-java/
     * <p>
     * calculate relative time from now on
     * like 5 days, 3 hours, 16 minutes  level 3
     */

    private static final Map<String, Long> times = new LinkedHashMap<>();

    private static final String FULL_SPLIT_TIME_FORMAT = "yyyy-MM-dd-HH-mm-ss";
    private static final String COMMON_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";


    /**
     * 在五秒误差内的一个统一时间
     */
    private static LocalDateTime UNIFORM_TIME;

    static {
        times.put(
                "year",
                TimeUnit.DAYS.toMillis(365)
        );
        times.put(
                "month",
                TimeUnit.DAYS.toMillis(30)
        );
        times.put(
                "week",
                TimeUnit.DAYS.toMillis(7)
        );
        times.put(
                "day",
                TimeUnit.DAYS.toMillis(1)
        );
        times.put(
                "hour",
                TimeUnit.HOURS.toMillis(1)
        );
        times.put(
                "minute",
                TimeUnit.MINUTES.toMillis(1)
        );
        times.put(
                "second",
                TimeUnit.SECONDS.toMillis(1)
        );
    }

    public static ByteBuffer currentTimeByteBuffer() {

        byte[] timeBytes = LocalDateTime
                .now(SYSTEM_TIME_ZONE_ID)
                .format(DateTimeFormatter.ofPattern(COMMON_TIME_FORMAT))
                .getBytes(StandardCharsets.UTF_8);

        return ByteBuffer.wrap(timeBytes);
    }

    public static LocalDateTime currentTime() {

        return LocalDateTime.now(SYSTEM_TIME_ZONE_ID);
    }

    public static double utcTimeStampOfDouble() {

        return (double) System.currentTimeMillis();
    }


    /**
     * @return 获取五秒时间误差内的统一时间
     */
    public static String UniformFormatTimeString() {

        if (ObjectUtils.isEmpty(UNIFORM_TIME)) {
            UNIFORM_TIME = currentTime();
            return forwardRoundingTimeString();
        }

        // 旧的时间和新时间之家误差小于5秒，那么使用同一个时间
        if (UNIFORM_TIME
                .plusSeconds(5)
                .isAfter(currentTime())) {
            UNIFORM_TIME = currentTime();
        }

        return localDateTimeFormatString(UNIFORM_TIME);
    }

    /**
     * 2023-12-22-11-37-41 should return 2023-12-22-11-37-40
     * 2023-12-22-11-37-47 should return 2023-12-22-11-37-45
     *
     * @return forward rounding time
     */
    private static String forwardRoundingTimeString() {

        LocalDateTime now = currentTime();

        int second = now.getSecond();
        while (second % 5 != 0) {
            now = now.minusSeconds(1);
            second = second - 1;
        }

        return localDateTimeFormatString(now);
    }

    /**
     * yyyy-MM-dd HH:mm:ss
     *
     * @return 格式化 去掉时间中的毫秒数
     */
    public static LocalDateTime currentFormatTime() {

        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern(COMMON_TIME_FORMAT);

        String format = LocalDateTime
                .now(SYSTEM_TIME_ZONE_ID)
                .format(ofPattern);

        return LocalDateTime.parse(format, ofPattern);
    }


    /**
     * @return UTC+8 [ yyyy-MM-dd-HH-mm-ss ] Time String
     */
    public static String currentFormatTimeString() {

        return localDateTimeFormatString(currentTime());
    }

    public static LocalDateTime cvFromDate(Date date) {

        // fix bug
        if (null == date) {
            return null;
        }

        return LocalDateTime.ofInstant(date.toInstant(),
                                       SYSTEM_TIME_ZONE_ID);
    }

    /**
     * @return UTC+8 [ yyyy-MM-dd HH:mm:ss ] Time String
     */
    public static String currentTimeString() {

        return LocalDateTime
                .now(SYSTEM_TIME_ZONE_ID)
                .format(DateTimeFormatter.ofPattern(COMMON_TIME_FORMAT));
    }


    /**
     * 输出格式为 COMMON_TIME_FORMAT "yyyy-MM-dd HH:mm:ss"
     *
     */
    public static String localDateTimeString(LocalDateTime time) {
        return time.format(DateTimeFormatter.ofPattern(COMMON_TIME_FORMAT));
    }

    /**
     * 输出格式为 FULL_SPLIT_TIME_FORMAT "yyyy-MM-dd-HH-mm-ss"
     *
     * @param time
     * @return
     */
    public static String localDateTimeFormatString(LocalDateTime time) {
        return time.format(DateTimeFormatter.ofPattern(FULL_SPLIT_TIME_FORMAT));
    }

    public static String toRelative(long duration, int maxLevel) {
        StringBuilder res = new StringBuilder();
        int level = 0;
        for (Map.Entry<String, Long> time : times.entrySet()) {
            long timeDelta = duration / time.getValue();
            if (timeDelta > 0) {
                res
                        .append(timeDelta)
                        .append(" ")
                        .append(time.getKey())
                        .append(timeDelta > 1 ? "s" : "")
                        .append(", ");
                duration -= time.getValue() * timeDelta;
                level++;
            }
            if (level == maxLevel) {
                break;
            }
        }
        if ("".equals(res.toString())) {
            return "0 seconds ago";
        } else {
            res.setLength(res.length() - 2);
            res.append(" ago");
            return res.toString();
        }
    }

    public static String toRelative(long duration) {
        return toRelative(
                duration,
                times.size()
        );
    }

    public static String toRelative(Date start, Date end) {
        assert start.after(end);
        return toRelative(end.getTime() - start.getTime());
    }

    public static String toRelative(Date start, Date end, int level) {
        assert start.after(end);
        return toRelative(
                end.getTime() - start.getTime(),
                level
        );
    }
}
