package com.onsemi.middleware.camel

import com.onsemi.middleware.UECommon.cache.hazelcast.spring.event.BecameClusterLeaderEvent
import com.onsemi.middleware.UECommon.model.ConnectionBean
import com.onsemi.middleware.UECommon.model.ConnectionBeanDb
import com.onsemi.middleware.UECommon.model.ConnectionJndiBean
import com.onsemi.middleware.UECommon.model.ConnectionJndiCompositeBean
import com.onsemi.middleware.UECommon.model.InterfaceJcaps
import com.onsemi.middleware.camel.context.KaravanPropertySource
import com.onsemi.middleware.camel.context.ObjectSavingManagementAgent
import com.onsemi.middleware.camel.context.connection.spring.ExecutorServiceManager
import com.onsemi.middleware.camel.event.listeners.CamelRunIdGeneratorListener
import com.onsemi.middleware.logging.dispatcher.LogDispatcher
import com.onsemi.middleware.messaging.InboundMessage
import com.onsemi.middleware.ue.execeng.runtime.ConnectionProvision
import com.onsemi.middleware.ue.execeng.services.serviceInterfaces.INodeServiceRuns
import org.apache.camel.CamelContext
import org.apache.camel.ServiceStatus
import org.apache.camel.processor.aggregate.AggregateProcessor
import org.apache.camel.spi.EventNotifier
import org.apache.camel.spi.ManagementStrategy
import org.apache.camel.spi.RouteController
import org.apache.camel.spi.ShutdownStrategy
import org.springframework.beans.factory.BeanFactory
import org.springframework.beans.factory.ListableBeanFactory
import spock.lang.Specification
import spock.lang.Subject

import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.function.Consumer

class UECamelBridgeImplTest extends Specification {

    @Subject
    UECamelBridgeImpl instance
    CamelContext context
    BeanFactory beanFactory
    ManagementStrategy managementStrategy
    ObjectSavingManagementAgent agent
    AggregateProcessor aggregateProcessor
    ShutdownStrategy shutdownStrategy
    InboundMessage inboundMessage
    LogDispatcher logDispatcher
    InterfaceJcaps anInterface
    INodeServiceRuns nodeServiceRuns
    ClassLoader serviceClassloader

    def setup(){
        nodeServiceRuns = Mock(INodeServiceRuns)
        logDispatcher = Mock(LogDispatcher)
        inboundMessage = Mock(InboundMessage)
        instance = new UECamelBridgeImpl()
        instance.log = logDispatcher
        instance.inMssg = inboundMessage
        anInterface = Mock(InterfaceJcaps)
        instance.anInterface = anInterface
        serviceClassloader = Mock(URLClassLoader)
        context = Mock(CamelContext)
        RouteController mockedRc = Mock(RouteController)
        context.getRouteController() >> {
            return mockedRc
        }
        context.getRoutes() >> {
            return Collections.emptyList()
        }
        shutdownStrategy = Mock(ShutdownStrategy)
        managementStrategy = Mock(ManagementStrategy)
        agent = Mock(ObjectSavingManagementAgent)
        agent.getProducers() >> {
            return new HashMap<>()
        }
        agent.getConsumers() >> {
            return new HashMap<>()
        }
        aggregateProcessor = Mock(AggregateProcessor)
        Map<String, AggregateProcessor> returnAgregateProcessors = new HashMap<>()
        returnAgregateProcessors.put("a", aggregateProcessor)
        agent.getAggregateProcessors() >> {
            return returnAgregateProcessors
        }
        managementStrategy.getManagementAgent() >> {
            return agent
        }
        context.getManagementStrategy() >> {
            return managementStrategy
        }
        context.getShutdownStrategy() >> {
            return shutdownStrategy
        }
        beanFactory = Mock(ListableBeanFactory)
        beanFactory.getBean(CamelContext.class) >> {
            return context
        }
        beanFactory.getBeansOfType(EventNotifier.class) >> {
            new HashMap<>()
        }
        beanFactory.getBean(InterfaceJcaps.class) >> {
            return anInterface
        }
        beanFactory.getBean(CamelRunIdGeneratorListener.class) >> {
            return Mock(CamelRunIdGeneratorListener)
        }
        instance.beanFactory = beanFactory
        nodeServiceRuns.getServiceClassloader(_ ) >> { argv ->
            return Optional.of(serviceClassloader)
        }

        instance.nodeServiceRuns = nodeServiceRuns
        java.util.function.Consumer<ClassLoader> setterOfCurrentCl = Mock(Consumer)
        instance.setterOfCurrentThreadClassLoader = setterOfCurrentCl
        def executorServiceManager = Mock(ExecutorServiceManager)
        executorServiceManager.newThread(_, _) >> Mock(Thread)
        executorServiceManager.newFixedThreadPool(_, instance.serviceShutdownThreadPoolName, 1) >> Executors.newFixedThreadPool(1)
        context.getExecutorServiceManager() >> executorServiceManager

    }

    def "Agregators flushed during inteded shutdown"(){
        when:
            instance.intendedShutdown()
        then:
            1* aggregateProcessor.forceCompletionOfAllGroups()
    }

    def "Service becoming cluster leader starts up and switches classloaders"(){
        given:
            anInterface.isServiceFlg() >> true
            anInterface.isSingleton() >> true
            context.getStatus() >> ServiceStatus.Initialized

        when:
            instance.onApplicationEvent(new BecameClusterLeaderEvent(new Object()))
        then:
            1* instance.setterOfCurrentThreadClassLoader.accept(serviceClassloader)
            1* context.start()
        then:

            1* instance.setterOfCurrentThreadClassLoader.accept(Thread.currentThread().getContextClassLoader())
    }

    def "Verify that even if consumer.stop() hangs that intended shutdown will be completed()"(){
        given:
            org.apache.camel.Consumer hangingConsumer = Mock(org.apache.camel.Consumer)
            hangingConsumer.stop() >> {
                while (true){
                    Thread.sleep(1000l)
                }
            }

            agent.getConsumers() >> Map.of("abc", hangingConsumer)
            instance.serviceForceShutdownTimeoutSec = 2
        when:
            def shutdowndownDone = false;
            def executor = Executors.newSingleThreadExecutor()
            def f = executor.submit {
                instance.intendedShutdown()
                shutdowndownDone = true
            }
            f.get(10, TimeUnit.SECONDS)
        then:
            shutdowndownDone == true
    }

    def "validate that force shutdown is called if context fails to stop"() {
        given:
            context.getInflightRepository() >> {
                throw new RuntimeException("mocked exception")
            }
            context.getManagementNameStrategy() >> {
                throw new RuntimeException("mocked exception 2")
            }
            UECamelBridgeImpl.SLEEP_PERIOD_WAIT_FOR_CAMEL_SEC = 1
            instance.forceShutdownTimeoutSec = 1
            instance.camelShutdownDelay = 1
        when:
            instance.gracefulCamelShutdown()
            instance.stopThread.join()
        then:
            1* logDispatcher.error(UECamelBridgeImpl.LOG_EX_CAUGHT_EXCEPTION_WHEN_SHUTTING_DOWN_GRACEFULLY, _, _)
            1* context.stop()
            noExceptionThrown()
    }
    def "Testing loading of properties"(){
        given:
        def bff = Mock(ListableBeanFactory);
        bff.getBeanNamesForType(MDWRouteBuilder.class) >> [];
        ConnectionJndiCompositeBean bean = Mock(ConnectionJndiCompositeBean);
        ConnectionBean conBean = Mock(ConnectionBean)
        ConnectionJndiBean jndiBean = Mock(ConnectionJndiBean)
        bean.getConnectionJndi() >> jndiBean
        jndiBean.getName() >> "string"
        bean.getConnection() >> conBean
        conBean.getClass() >> ConnectionBeanDb.class;
        instance.prov = Mock(ConnectionProvision)
        KaravanPropertySource karavanPropertySource = Mock(KaravanPropertySource)
        karavanPropertySource.getPropertySubMap(_) >> Map.of("jndi.db.servername", "localhost")
        instance.karavanProperties = karavanPropertySource;
        karavanPropertySource.setProperty(_,_) >> "servername"
        karavanPropertySource.getProperties() >> [:]
        instance.prov.getInterfaceConnections() >> [bean]
        InterfaceJcaps interfaceJcaps = Mock(InterfaceJcaps);
        instance.anInterface = interfaceJcaps;
        interfaceJcaps.getName() >>"name"

        when:
        instance.addRoutesFromRouteBuilders(bff);


        then:
        1 * karavanPropertySource.setProperty(_, _);
        1 * instance.setProperty("jndi.db.servername", "localhost")
        karavanPropertySource.get("jndi.db.servername").equals("localhost")
        1 * context.addRoutes(_)
        1 * context.start()


    }

}
