package com.lanyuanxiaoyao.service.configuration.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.common.Constants;
import com.lanyuanxiaoyao.service.common.entity.compaction.ScheduleJob;
import com.lanyuanxiaoyao.service.configuration.entity.queue.QueueItem;
import dev.failsafe.Failsafe;
import dev.failsafe.RetryPolicy;
import java.time.Duration;
import java.util.List;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;

/**
 * 队列操作
 *
 * @author lanyuanxiaoyao
 * @date 2023-05-08
 */
public class QueueUtil {
    private static final Logger logger = LoggerFactory.getLogger(QueueUtil.class);
    private static final RetryPolicy<Object> RETRY = RetryPolicy.builder()
            .handle(Throwable.class)
            .withDelay(Duration.ofSeconds(5))
            .withMaxAttempts(12)
            .build();

    private static String getQueueUrl(DiscoveryClient client) {
        List<ServiceInstance> instances = client.getInstances("service-queue");
        if (ObjectUtil.isEmpty(instances)) {
            logger.info("Current instances: {}", String.join(",", client.getServices()));
            throw new RuntimeException("Queue instance not found");
        }
        return instances.get(0).getUri().toString();
    }

    public static ImmutableList<String> names(DiscoveryClient client, ObjectMapper mapper) {
        return Failsafe.with(RETRY)
                .get(() -> {
                    try (HttpResponse response = HttpUtil.createGet(URLUtil.completeUrl(getQueueUrl(client), "/names"))
                            .basicAuth(Constants.SPRING_SECURITY_USERNAME, Constants.SPRING_SECURITY_PASSWORD_PLAIN)
                            .execute()) {
                        if (response.isOk()) {
                            return mapper.readValue(response.body(), new TypeReference<ImmutableList<String>>() {
                            });
                        }
                        return Lists.immutable.empty();
                    }
                });
    }

    public static Boolean add(DiscoveryClient client, ObjectMapper mapper, String name, QueueItem<ScheduleJob> job) {
        try {
            if (ObjectUtil.isNull(job)) {
                logger.warn("Job cannot be null");
                return false;
            }
            String body = mapper.writeValueAsString(job);
            if (StrUtil.isBlank(body)) {
                logger.error("Body cannot be blank");
                return false;
            }
            return Failsafe.with(RETRY)
                    .get(() -> {
                        try (HttpResponse response = HttpUtil.createPost(URLUtil.completeUrl(getQueueUrl(client), StrUtil.format("/queue/add?name={}", name)))
                                .basicAuth(Constants.SPRING_SECURITY_USERNAME, Constants.SPRING_SECURITY_PASSWORD_PLAIN)
                                .body(body)
                                .execute()) {
                            if (response.isOk()) {
                                return Boolean.valueOf(response.body());
                            }
                            return false;
                        }
                    });
        } catch (Throwable e) {
            logger.error("Add job to queue error", e);
        }
        return false;
    }

    public static QueueItem<ScheduleJob> poll(DiscoveryClient client, ObjectMapper mapper, String name) {
        try {
            return Failsafe.with(RETRY)
                    .get(() -> {
                        try (HttpResponse response = HttpUtil.createGet(URLUtil.completeUrl(getQueueUrl(client), StrUtil.format("/queue/poll?name={}", name)))
                                .basicAuth(Constants.SPRING_SECURITY_USERNAME, Constants.SPRING_SECURITY_PASSWORD_PLAIN)
                                .execute()) {
                            if (response.isOk()) {
                                String body = response.body();
                                if (StrUtil.isNotBlank(body)) {
                                    try {
                                        return mapper.readValue(body, new TypeReference<QueueItem<ScheduleJob>>() {
                                        });
                                    } catch (Throwable error) {
                                        logger.error("Schedule job parse error", error);
                                    }
                                }
                            }
                            return null;
                        }
                    });
        } catch (Throwable e) {
            logger.error("Poll job from queue error", e);
        }
        return null;
    }

    public static Boolean isEmpty(DiscoveryClient client, String name) {
        try {
            try (HttpResponse response = HttpUtil.createGet(URLUtil.completeUrl(getQueueUrl(client), StrUtil.format("/queue/is_empty?name={}", name)))
                    .basicAuth(Constants.SPRING_SECURITY_USERNAME, Constants.SPRING_SECURITY_PASSWORD_PLAIN)
                    .execute()) {
                if (response.isOk()) {
                    String body = response.body();
                    if (StrUtil.isNotBlank(body)) {
                        return Boolean.valueOf(body);
                    }
                }
                return true;
            }
        } catch (Throwable e) {
            logger.error("Query empty failure", e);
        }
        return true;
    }

    public static Integer size(DiscoveryClient client, String name) {
        try {
            try (HttpResponse response = HttpUtil.createGet(URLUtil.completeUrl(getQueueUrl(client), StrUtil.format("/queue/size?name={}", name)))
                    .basicAuth(Constants.SPRING_SECURITY_USERNAME, Constants.SPRING_SECURITY_PASSWORD_PLAIN)
                    .execute()) {
                if (response.isOk()) {
                    String body = response.body();
                    if (StrUtil.isNotBlank(body)) {
                        return Integer.valueOf(body);
                    }
                }
                return -1;
            }
        } catch (Throwable e) {
            logger.error("Query empty failure", e);
        }
        return -1;
    }

    public static Boolean remove(DiscoveryClient client, String name, String id) {
        try {
            try (HttpResponse response = HttpUtil.createGet(URLUtil.completeUrl(getQueueUrl(client), StrUtil.format("/queue/remove_all_id?name={}&id={}", name, id)))
                    .basicAuth(Constants.SPRING_SECURITY_USERNAME, Constants.SPRING_SECURITY_PASSWORD_PLAIN)
                    .execute()) {
                if (response.isOk()) {
                    return Boolean.valueOf(response.body());
                }
                return true;
            }
        } catch (Throwable e) {
            logger.error(StrUtil.format("Remove {} failure", id), e);
        }
        return false;
    }

    public static Boolean remove(DiscoveryClient client, ObjectMapper mapper, String name, ImmutableList<String> ids) {
        try {
            String body = mapper.writeValueAsString(ids);
            if (StrUtil.isBlank(body)) {
                logger.error("Body cannot be blank");
                return false;
            }
            try (HttpResponse response = HttpUtil.createPost(URLUtil.completeUrl(getQueueUrl(client), StrUtil.format("/queue/remove_all_id?name={}", name)))
                    .basicAuth(Constants.SPRING_SECURITY_USERNAME, Constants.SPRING_SECURITY_PASSWORD_PLAIN)
                    .body(body)
                    .execute()) {
                if (response.isOk()) {
                    return Boolean.valueOf(response.body());
                }
                return true;
            }
        } catch (Throwable e) {
            logger.error(StrUtil.format("Remove {} failure", ids), e);
        }
        return false;
    }

    public interface Function<F, T> {
        T apply(F f) throws Throwable;
    }
}
