/*
 * 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.threads.impl;

import jakarta.enterprise.concurrent.ManageableThread;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.resource.thread.ManagedScheduledExecutorServiceImplFactory;
import org.apache.openejb.ri.sp.PseudoSecurityService;
import org.apache.openejb.spi.SecurityService;
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class ManagedThreadFactoryVirtualTest {

    @BeforeClass
    public static void setup() {
        SystemInstance.get().setComponent(SecurityService.class, new PseudoSecurityService());
    }

    @AfterClass
    public static void reset() {
        SystemInstance.reset();
    }

    @Test
    public void platformThreadImplementsManageableThread() {
        final ContextServiceImpl contextService = ContextServiceImplFactory.newDefaultContextService();
        final ManagedThreadFactoryImpl factory = new ManagedThreadFactoryImpl("test-", null, contextService, false);

        final Thread thread = factory.newThread(() -> {});
        assertNotNull(thread);
        assertTrue("Platform thread should implement ManageableThread",
                thread instanceof ManageableThread);
    }

    @Test
    public void virtualThreadDoesNotImplementManageableThread() {
        Assume.assumeTrue("Virtual threads require Java 21+", VirtualThreadHelper.isSupported());

        final ContextServiceImpl contextService = ContextServiceImplFactory.newDefaultContextService();
        final ManagedThreadFactoryImpl factory = new ManagedThreadFactoryImpl("test-vt-", null, contextService, true);

        final Thread thread = factory.newThread(() -> {});
        assertNotNull(thread);
        // Spec 3.4.4: virtual threads do NOT implement ManageableThread
        assertFalse("Virtual thread must NOT implement ManageableThread",
                thread instanceof ManageableThread);
    }

    @Test
    public void virtualThreadExecutesTask() {
        Assume.assumeTrue("Virtual threads require Java 21+", VirtualThreadHelper.isSupported());

        final ContextServiceImpl contextService = ContextServiceImplFactory.newDefaultContextService();
        final ManagedThreadFactoryImpl factory = new ManagedThreadFactoryImpl("test-vt-", null, contextService, true);

        final CountDownLatch latch = new CountDownLatch(1);
        final Thread thread = factory.newThread(latch::countDown);
        thread.start();

        try {
            assertTrue("Virtual thread should execute the task",
                    latch.await(5, TimeUnit.SECONDS));
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            fail("Interrupted");
        }
    }

    @Test
    public void virtualFactoryFallsBackToPlatformForForkJoinPool() {
        Assume.assumeTrue("Virtual threads require Java 21+", VirtualThreadHelper.isSupported());

        final ContextServiceImpl contextService = ContextServiceImplFactory.newDefaultContextService();
        final ManagedThreadFactoryImpl factory = new ManagedThreadFactoryImpl("test-vt-", null, contextService, true);

        // ForkJoinWorkerThread cannot be virtual — should fall back to platform thread
        final ForkJoinPool pool = new ForkJoinPool(1, factory, null, false);
        try {
            final java.util.concurrent.Future<String> result = pool.submit(() -> "ok");
            assertEquals("ForkJoinPool should work with virtual factory", "ok", result.get(5, java.util.concurrent.TimeUnit.SECONDS));
        } catch (final Exception e) {
            fail("ForkJoinPool with virtual factory should not throw: " + e);
        } finally {
            pool.shutdown();
        }
    }

    @Test
    public void isVirtualReflectsConstructorParam() {
        final ContextServiceImpl contextService = ContextServiceImplFactory.newDefaultContextService();

        final ManagedThreadFactoryImpl platformFactory = new ManagedThreadFactoryImpl("p-", null, contextService, false);
        assertFalse(platformFactory.isVirtual());

        final ManagedThreadFactoryImpl virtualFactory = new ManagedThreadFactoryImpl("v-", null, contextService, true);
        assertTrue(virtualFactory.isVirtual());
    }

    /**
     * Regression: the default-factory branch in
     * {@link ManagedScheduledExecutorServiceImplFactory#create()} used to instantiate
     * {@code ManagedThreadFactoryImpl} via reflection on its no-arg constructor, which
     * hard-codes {@code virtual=false}. As a result, {@code virtual=true} on the factory
     * was silently ignored unless reflection failed and the catch block kicked in.
     */
    @Test
    public void scheduledFactoryHonorsVirtualFlagOnDefaultThreadFactoryPath() throws Exception {
        Assume.assumeTrue("Virtual threads require Java 21+", VirtualThreadHelper.isSupported());

        final ManagedScheduledExecutorServiceImplFactory factory = new ManagedScheduledExecutorServiceImplFactory();
        factory.setVirtual(true);
        final ManagedScheduledExecutorServiceImpl mses = factory.create();
        try {
            // Per Concurrency 3.1 §3.4.4 virtual threads do NOT implement ManageableThread.
            final Future<Boolean> future = mses.submit(() -> !(Thread.currentThread() instanceof ManageableThread));
            assertTrue("Scheduled factory with virtual=true must produce virtual threads",
                    future.get(5, TimeUnit.SECONDS));
        } finally {
            shutdown(mses);
        }
    }

    @Test
    public void scheduledFactoryDefaultsToPlatformThreads() throws Exception {
        final ManagedScheduledExecutorServiceImplFactory factory = new ManagedScheduledExecutorServiceImplFactory();
        final ManagedScheduledExecutorServiceImpl mses = factory.create();
        try {
            final Future<Boolean> future = mses.submit(() -> Thread.currentThread() instanceof ManageableThread);
            assertTrue("Default scheduled factory must produce platform (ManageableThread) threads",
                    future.get(5, TimeUnit.SECONDS));
        } finally {
            shutdown(mses);
        }
    }

    private static void shutdown(final ManagedScheduledExecutorServiceImpl mses) {
        final ScheduledExecutorService delegate = mses.getDelegate();
        if (delegate != null) {
            delegate.shutdownNow();
        }
    }
}
