package ru.kirillius.pf.sdn.core;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Instantiates and manages the lifecycle of registered application services.
 */
public class ServiceManager implements Closeable {
    private final Map<Class<? extends AppService>, AppService> services = new LinkedHashMap<>();

    /**
     * Creates a manager and eagerly instantiates the provided service classes.
     */
    public ServiceManager(Context context, Collection<Class<? extends AppService>> serviceClasses) {
        Objects.requireNonNull(context, "context");
        if (serviceClasses == null) {
            return;
        }
        for (var serviceClass : serviceClasses) {
            if (serviceClass == null) {
                continue;
            }
            services.put(serviceClass, instantiateService(context, serviceClass));
        }
    }

    /**
     * Retrieves a registered service by type.
     *
     * @param serviceClass target service class.
     * @param <S>          service subtype.
     * @return instance if available, otherwise {@code null}.
     */
    public <S extends AppService> S getService(Class<S> serviceClass) {
        var service = services.get(serviceClass);
        if (service == null) {
            return null;
        }
        return serviceClass.cast(service);
    }

    /**
     * Closes all managed services, propagating the first {@link IOException} encountered.
     */
    @Override
    public void close() throws IOException {
        IOException failure = null;
        for (var service : services.values()) {
            try {
                service.close();
            } catch (IOException e) {
                if (failure == null) {
                    failure = e;
                } else {
                    failure.addSuppressed(e);
                }
            }
        }
        if (failure != null) {
            throw failure;
        }
    }

    /**
     * Creates a service instance using either context-aware or default constructor.
     */
    private AppService instantiateService(Context context, Class<? extends AppService> serviceClass) {
        try {
            var constructor = resolveConstructor(serviceClass);
            constructor.setAccessible(true);
            return constructor.getParameterCount() == 1
                    ? constructor.newInstance(context)
                    : constructor.newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new IllegalStateException("Failed to instantiate service: " + serviceClass.getName(), e);
        }
    }

    /**
     * Resolves a usable constructor favouring context-aware signatures.
     */
    private Constructor<? extends AppService> resolveConstructor(Class<? extends AppService> serviceClass) {
        try {
            return serviceClass.getDeclaredConstructor(Context.class);
        } catch (NoSuchMethodException ignored) {
            try {
                return serviceClass.getDeclaredConstructor();
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("No suitable constructor found for service: " + serviceClass.getName(), e);
            }
        }
    }
}
