package org.example.Utils;

import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public final class ResourceExecutors {
    private static final int DEFAULT_FILE_CORE_SIZE = 2;
    private static final int DEFAULT_FILE_MAX_SIZE = 4;
    private static final int DEFAULT_FILE_QUEUE_CAPACITY = 200;
    private static final int DEFAULT_PUSH_CORE_SIZE = 4;
    private static final int DEFAULT_PUSH_MAX_SIZE = 16;
    private static final int DEFAULT_PUSH_QUEUE_CAPACITY = 500;
    private static final long KEEP_ALIVE_SECONDS = 60L;

    private static ThreadPoolExecutor fileCleanupExecutor = newExecutor(
            "file-cleanup",
            DEFAULT_FILE_CORE_SIZE,
            DEFAULT_FILE_MAX_SIZE,
            DEFAULT_FILE_QUEUE_CAPACITY
    );
    private static ThreadPoolExecutor pushExecutor = newExecutor(
            "push",
            DEFAULT_PUSH_CORE_SIZE,
            DEFAULT_PUSH_MAX_SIZE,
            DEFAULT_PUSH_QUEUE_CAPACITY
    );

    private ResourceExecutors() {
    }

    public static synchronized void configure(
            int fileCoreSize,
            int fileMaxSize,
            int fileQueueCapacity,
            int pushCoreSize,
            int pushMaxSize,
            int pushQueueCapacity
    ) {
        replaceExecutors(
                fileCoreSize,
                fileMaxSize,
                fileQueueCapacity,
                pushCoreSize,
                pushMaxSize,
                pushQueueCapacity
        );
    }

    public static Future<?> submitFileCleanup(Runnable task) {
        return fileCleanupExecutor.submit(Objects.requireNonNull(task, "task must not be null"));
    }

    public static Future<?> submitPush(Runnable task) {
        return pushExecutor.submit(Objects.requireNonNull(task, "task must not be null"));
    }

    public static synchronized void shutdownFileCleanup() {
        shutdown(fileCleanupExecutor);
    }

    public static synchronized void shutdownAll() {
        shutdown(fileCleanupExecutor);
        shutdown(pushExecutor);
    }

    public static synchronized void configureForTests(
            int fileCoreSize,
            int fileMaxSize,
            int fileQueueCapacity,
            int pushCoreSize,
            int pushMaxSize,
            int pushQueueCapacity
    ) {
        replaceExecutors(
                fileCoreSize,
                fileMaxSize,
                fileQueueCapacity,
                pushCoreSize,
                pushMaxSize,
                pushQueueCapacity
        );
    }

    public static synchronized void resetForTests() {
        replaceExecutors(
                DEFAULT_FILE_CORE_SIZE,
                DEFAULT_FILE_MAX_SIZE,
                DEFAULT_FILE_QUEUE_CAPACITY,
                DEFAULT_PUSH_CORE_SIZE,
                DEFAULT_PUSH_MAX_SIZE,
                DEFAULT_PUSH_QUEUE_CAPACITY
        );
    }

    public static ThreadPoolExecutor fileCleanupExecutorForTests() {
        return fileCleanupExecutor;
    }

    public static ThreadPoolExecutor pushExecutorForTests() {
        return pushExecutor;
    }

    private static void replaceExecutors(
            int fileCoreSize,
            int fileMaxSize,
            int fileQueueCapacity,
            int pushCoreSize,
            int pushMaxSize,
            int pushQueueCapacity
    ) {
        ThreadPoolExecutor oldFileExecutor = fileCleanupExecutor;
        ThreadPoolExecutor oldPushExecutor = pushExecutor;
        fileCleanupExecutor = newExecutor("file-cleanup", fileCoreSize, fileMaxSize, fileQueueCapacity);
        pushExecutor = newExecutor("push", pushCoreSize, pushMaxSize, pushQueueCapacity);
        shutdown(oldFileExecutor);
        shutdown(oldPushExecutor);
    }

    private static ThreadPoolExecutor newExecutor(String name, int coreSize, int maxSize, int queueCapacity) {
        int safeCore = Math.max(1, coreSize);
        int safeMax = Math.max(safeCore, maxSize);
        int safeQueueCapacity = Math.max(1, queueCapacity);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                safeCore,
                safeMax,
                KEEP_ALIVE_SECONDS,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(safeQueueCapacity),
                new NamedDaemonThreadFactory("resource-" + name),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        executor.allowCoreThreadTimeOut(false);
        return executor;
    }

    private static void shutdown(ThreadPoolExecutor executor) {
        if (executor != null && !executor.isShutdown()) {
            executor.shutdown();
        }
    }

    private static class NamedDaemonThreadFactory implements ThreadFactory {
        private final String prefix;
        private final AtomicInteger sequence = new AtomicInteger(1);

        private NamedDaemonThreadFactory(String prefix) {
            this.prefix = prefix;
        }

        @Override
        public Thread newThread(Runnable runnable) {
            Thread thread = new Thread(runnable, prefix + "-" + sequence.getAndIncrement());
            thread.setDaemon(true);
            return thread;
        }
    }
}
