/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.openejb.resource.thread;

import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.threads.impl.ContextServiceImpl;
import org.apache.openejb.threads.impl.ContextServiceImplFactory;
import org.apache.openejb.threads.impl.ManagedScheduledExecutorServiceImpl;
import org.apache.openejb.threads.impl.ManagedThreadFactoryImpl;
import org.apache.openejb.threads.reject.CURejectHandler;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;

import jakarta.enterprise.concurrent.ManagedThreadFactory;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

public class ManagedScheduledExecutorServiceImplFactory {

    private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB, ManagedScheduledExecutorServiceImplFactory.class);

    private static final String DEFAULT_MES = "java:comp/DefaultManagedExecutorService";
    private static final String DEFAULT_MSES = "java:comp/DefaultManagedScheduledExecutorService";

    public static ManagedScheduledExecutorServiceImpl lookup(String name) {
        // If the caller passes the default ManagedExecutorService JNDI name, map it to the
        // default ManagedScheduledExecutorService instead
        final boolean isDefault = DEFAULT_MES.equals(name) || DEFAULT_MSES.equals(name);
        if (DEFAULT_MES.equals(name)) {
            name = DEFAULT_MSES;
        }

        // Try direct JNDI lookup first
        try {
            final Object obj = InitialContext.doLookup(name);
            if (obj instanceof ManagedScheduledExecutorServiceImpl mses) {
                return mses;
            }
        } catch (final NamingException ignored) {
            // fall through to container JNDI
        }

        // Try container JNDI with resource ID
        try {
            final Context ctx = SystemInstance.get().getComponent(ContainerSystem.class).getJNDIContext();
            String resourceId;
            if (DEFAULT_MSES.equals(name)) {
                resourceId = "Default Scheduled Executor Service";
            } else {
                // Strip java: prefix to match how resources are registered (via cleanUpName)
                resourceId = name.startsWith("java:") ? name.substring("java:".length()) : name;
            }

            final Object obj = ctx.lookup("openejb/Resource/" + resourceId);
            if (obj instanceof ManagedScheduledExecutorServiceImpl mses) {
                return mses;
            }
        } catch (final NamingException ignored) {
            // fall through
        }

        // Only fall back to default for the well-known default names.
        // For custom/invalid names, throw so the caller gets RejectedExecutionException.
        if (isDefault) {
            LOGGER.debug("Cannot lookup ManagedScheduledExecutorService '" + name + "', creating default instance");
            return new ManagedScheduledExecutorServiceImplFactory().create();
        }

        throw new IllegalArgumentException("Cannot find ManagedScheduledExecutorService with name '" + name + "'");
    }

    private int core = 5;
    private int scheduledAsyncCore = 5;
    private String threadFactory = ManagedThreadFactoryImpl.class.getName();
    private boolean virtual;

    private String context;

    public ManagedScheduledExecutorServiceImpl create(final ContextServiceImpl contextService) {
        final Pools pools = createScheduledExecutorServicePools();
        return new ManagedScheduledExecutorServiceImpl(pools.primary, contextService, pools.secondary, true);
    }
    public ManagedScheduledExecutorServiceImpl create() {
        final Pools pools = createScheduledExecutorServicePools();
        return new ManagedScheduledExecutorServiceImpl(pools.primary, ContextServiceImplFactory.lookupOrDefault(context),
                pools.secondary, true);
    }

    private Pools createScheduledExecutorServicePools() {
        ManagedThreadFactory managedThreadFactory;
        try {
            // For the default factory, bypass reflective instantiation so the configured
            // virtual flag is honored — the no-arg constructor hardcodes virtual=false.
            managedThreadFactory = ManagedThreadFactoryImpl.class.getName().equals(threadFactory) ?
                    new ManagedThreadFactoryImpl(ManagedThreadFactoryImpl.DEFAULT_PREFIX, null, ContextServiceImplFactory.lookupOrDefault(context), virtual) :
                    ThreadFactories.findThreadFactory(threadFactory);
        } catch (final Exception e) {
            Logger.getInstance(LogCategory.OPENEJB, ManagedScheduledExecutorServiceImplFactory.class).warning("Unable to create configured thread factory: " + threadFactory, e);
            managedThreadFactory = new ManagedThreadFactoryImpl(ManagedThreadFactoryImpl.DEFAULT_PREFIX, null, ContextServiceImplFactory.lookupOrDefault(context), virtual);
        }

        // Primary pool — regular async submissions; corePoolSize follows maxAsync.
        final ScheduledExecutorService primary =
                new ScheduledThreadPoolExecutor(core, managedThreadFactory, CURejectHandler.INSTANCE);
        // Secondary pool — scheduled @Asynchronous firings. Per Concurrency 3.1 §3.1 these
        // must NOT be throttled by maxAsync, so size the pool independently while reusing
        // the same stateless thread factory (preserves naming / virtual / priority).
        final ScheduledExecutorService secondary =
                new ScheduledThreadPoolExecutor(scheduledAsyncCore, managedThreadFactory, CURejectHandler.INSTANCE);
        return new Pools(primary, secondary);
    }

    public void setCore(final int core) {
        this.core = core;
    }

    public void setScheduledAsyncCore(final int scheduledAsyncCore) {
        this.scheduledAsyncCore = scheduledAsyncCore;
    }

    private record Pools(ScheduledExecutorService primary, ScheduledExecutorService secondary) {
    }

    public void setThreadFactory(final String threadFactory) {
        this.threadFactory = threadFactory;
    }

    public String getContext() {
        return context;
    }

    public void setContext(final String context) {
        this.context = context;
    }

    public boolean isVirtual() {
        return virtual;
    }

    public void setVirtual(final boolean virtual) {
        this.virtual = virtual;
    }
}
