package ru.kirillius.pf.sdn.core.Subscription;

import lombok.Getter;
import ru.kirillius.pf.sdn.core.AppService;
import ru.kirillius.pf.sdn.core.Context;
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
import ru.kirillius.utils.logging.SystemLogger;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Resolves subscription repositories, builds aggregated resource bundles, and publishes update events.
 */
public class SubscriptionService extends AppService {
    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private final Map<Class<? extends SubscriptionProvider>, SubscriptionProvider> providerCache = new ConcurrentHashMap<>();

    private final AtomicReference<Future<?>> updateProcess = new AtomicReference<>();

    @Getter
    private final NetworkResourceBundle outputResources = new NetworkResourceBundle();
    @Getter
    private final List<String> autoResolvingDomains = new CopyOnWriteArrayList<>();

    public SubscriptionService(Context context) {
        super(context);
    }

    /**
     * Indicates whether a subscription update job is currently running.
     */
    public boolean isUpdatingNow() {
        var future = updateProcess.get();
        return future != null && !future.isDone() && !future.isCancelled();
    }

    @Getter
    private final Map<String, NetworkResourceBundle> availableResources = new ConcurrentHashMap<>();

    @SuppressWarnings("unchecked")
    private <T extends SubscriptionProvider> T getProvider(Class<T> providerType) {
        if (!providerCache.containsKey(providerType)) {
            providerCache.put(providerType, SubscriptionProvider.instantiate(providerType, context));
        }
        return (T) providerCache.get(providerType);
    }

    /**
     * Starts a background task that refreshes subscription repositories and aggregates resources.
     */
    public synchronized void triggerUpdate() {
        if (isUpdatingNow()) {
            return;
        }
        updateProcess.set(executor.submit(() -> {



            var available = new HashMap<String, NetworkResourceBundle>();

            var bundle = new NetworkResourceBundle();

            var config = context.getConfig();
            var subscribedResources = config.getSubscribedResources();
            for (var repoConfig : config.getSubscriptions()) {
                var providerType = repoConfig.getType();
                @SuppressWarnings({"unchecked", "rawtypes"}) var provider = getProvider((Class) providerType);
                try {
                    var resources = provider.getResources(repoConfig);
                    resources.keySet().forEach(key -> {
                        var resourceName = repoConfig.getName() + ":" + key;
                        //добавляем только выбранные ресурсы
                        if (subscribedResources.contains(resourceName)) {
                            bundle.add(resources.get(key));
                        }
                        available.put(resourceName, resources.get(key));
                    });
                } catch (Exception e) {
                    SystemLogger.error("Error fetching provider " + provider, CTX, e);
                }
            }

            availableResources.clear();
            availableResources.putAll(available);
            outputResources.clear();
            outputResources.add(bundle);

            available.values().forEach(b -> {
                if (b.isResolveDomains()) {
                    autoResolvingDomains.addAll(b.getDomains());
                }
            });
            try {
                context.getEventsHandler().getSubscriptionsUpdateEvent().invoke(outputResources);
            } catch (Exception e) {
                SystemLogger.error("Error on update event", CTX, e);
            }
        }));
    }

    private final static String CTX = SubscriptionService.class.getSimpleName();

    /**
     * Shuts down the executor used for update tasks.
     */
    @Override
    public void close() throws IOException {
        executor.shutdown();
    }
}
