package ru.kirillius.pf.sdn.service;

import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.stereotype.Service;
import ru.kirillius.pf.sdn.api.Networking.AutonomousSystemResolver;
import ru.kirillius.pf.sdn.api.Networking.AutonomousSystemResolverApi;
import ru.kirillius.pf.sdn.api.Networking.IPv4Subnet;
import ru.kirillius.pf.sdn.api.dto.IPQueryInfo;

import java.io.IOException;
import java.net.Inet4Address;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;

@Setter
@RequiredArgsConstructor
@Service
public class AutonomousSystemResolverService implements AutonomousSystemResolver {
    private final ExecutorService executor;
    private final List<AutonomousSystemResolverApi> apis = new CopyOnWriteArrayList<>();
    private final AtomicReference<AutonomousSystemResolverApi> currentApi = new AtomicReference<>();

    private synchronized void selectNextApi() {
        if (apis.isEmpty()) {
            return;
        }
        var index = apis.indexOf(currentApi.get());
        if (index == -1 || index == apis.size() - 1) {
            currentApi.set(apis.getFirst());
            return;
        }
        currentApi.set(apis.get(index + 1));
    }

    @Override
    public void registerApi(AutonomousSystemResolverApi api) {
        apis.add(api);
        if (currentApi.get() == null) {
            currentApi.set(api);
        }
    }

    @Override
    public Future<List<IPv4Subnet>> getPrefixes(int as) {
        return executor.submit(() -> {
            checkApis();
            return executeWithFallbackSelection(() -> currentApi.get().getPrefixes(as));
        });
    }

    private void checkApis() throws IOException {
        if (apis.isEmpty()) {
            throw new IOException("You're trying to resolve an AS without any resolvers");
        }
        if (currentApi.get() == null) {
            selectNextApi();
        }
    }

    private <T> T executeWithFallbackSelection(Callable<T> callable) throws IOException {
        var retries = 0;
        while (retries < apis.size()) {
            try {
                return callable.call();
            } catch (Exception e) {
                retries++;
                selectNextApi();
            }
        }
        throw new IOException("Unable to perform operation on every AS resolver");
    }

    @Override
    public Future<IPQueryInfo> getAddressInfo(Inet4Address address) {
        return executor.submit(() -> {
            checkApis();
            return executeWithFallbackSelection(() -> currentApi.get().queryAddress(address));
        });
    }
}
