package com.onsemi.middleware.camel.context.connection.spring;

import java.io.File;
import java.util.List;
import java.util.Optional;

import javax.sql.DataSource;

import com.onsemi.middleware.camel.context.GitLabKaravanSourceLoader;
import com.onsemi.middleware.camel.context.KaravanPropertySource;
import org.apache.camel.CamelContext;
import org.apache.camel.impl.engine.DefaultPackageScanClassResolver;
import org.apache.camel.spi.InflightRepository;
import org.apache.camel.spring.SpringCamelContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;

import com.onsemi.middleware.UECommon.cache.InterfaceRunRequest;
import com.onsemi.middleware.UECommon.model.Interface;
import com.onsemi.middleware.UECommon.model.InterfaceJcaps;
import com.onsemi.middleware.UECommon.model.StateControllableInterface;
import com.onsemi.middleware.camel.EpXReconfigurationHelper;
import com.onsemi.middleware.camel.UECamelBridgeImpl;
import com.onsemi.middleware.camel.UECamelBridgeImplArchetypeIntegration;
import com.onsemi.middleware.camel.context.OnsemiCamelShutdownStrategy;
import com.onsemi.middleware.camel.context.ServiceContextShutdownHelper;
import com.onsemi.middleware.camel.context.connection.ComponentFactory;
import com.onsemi.middleware.camel.context.connection.FactoryRouter;
import com.onsemi.middleware.camel.context.setter.InterfacePropertiesToExchangeSetter;
import com.onsemi.middleware.camel.context.setter.PayloadValuesToExchangeSetter;
import com.onsemi.middleware.camel.context.transaction.AllOrNothingTransactionManager;
import com.onsemi.middleware.camel.context.transaction.ContextTransactionHandler;
import com.onsemi.middleware.camel.context.transaction.TransactionNeededDetector;
import com.onsemi.middleware.camel.context.transaction.config.SemiAutomatedTransactionConfiguration;
import com.onsemi.middleware.camel.context.transaction.spring.ConditionalOnNonSpringTransactionHandling;
import com.onsemi.middleware.camel.convertor.BeanNameToDataSourceBeanConverter;

@AutoConfiguration
@ConditionalOnInterfaceContext
@Import({ SemiAutomatedTransactionConfiguration.class, UERuntimeDaoInterfaceContextConfig.class, UEClassInterfInterfaceContextConfiguration.class })
@ComponentScan(basePackageClasses = { CamelShutdownListener.class, UECamelBridge.class, OnsemiJmxManagementStrategyFactory.class,
		AllBeansLazyJunitBeanFactoryPostProcessor.class, UECamelBridgeImpl.class, CamelShutdownListener.class, MapBackedUpEndpointInject.class })
public class UECamelBridgeConfiguration implements ApplicationContextAware {

	private ApplicationContext applicationContext;

	@Bean("camelContext")
	public CamelContext getContext(ApplicationContext context,
								   Interface anInterface,
								   InboundMessage inboundMessage,
								   LogDispatcher logDispatcher,
								   ThreadLocalsSupport threadLocalsSupport,
								   InterfaceRunRequest interfaceRunRequest,
								   CurrentRunIdHolder runIdHolder,
								   AdditionalFieldHolderRegistry registry) {
		SpringCamelContext camelContext = new SpringCamelContext();
		camelContext.setApplicationContext(context);
		camelContext.setAutoStartup(true);
		camelContext.getTypeConverterRegistry()
					.addTypeConverter(DataSource.class,
									  String.class,
									  new BeanNameToDataSourceBeanConverter(camelContext.getCamelContextReference(),
											  								new DefaultPackageScanClassResolver(),
																			camelContext.getInjector(),
																			camelContext.isLoadTypeConverters(),
																			camelContext.isTypeConverterStatisticsEnabled(),
											  								context));
		camelContext.setShutdownStrategy(new OnsemiCamelShutdownStrategy());
		String camelContextName = generateContextName(anInterface, inboundMessage);
		camelContext.getCamelContextExtension().setName(camelContextName);
		SpringCamelContext.setNoStart(true);
		camelContext.setMessageHistory(true);
		CamelAppenderThreadLocalLogging.setLogDispatcherThreadLocal(logDispatcher);
		String runId = getRunIdFromBeanFactory(inboundMessage);
		CamelAppenderThreadLocalLogging.setLogDispatcherForRunId(logDispatcher, runId);

		InflightRepository ifRepo = camelContext.getInflightRepository();
		ifRepo.setInflightBrowseEnabled(true);
		camelContext.setExecutorServiceManager(applicationContext.getBean(ExecutorServiceManager.class, threadLocalsSupport, logDispatcher, camelContext, interfaceRunRequest, runIdHolder, registry));
		camelContext.setAllowUseOriginalMessage(true);
		return camelContext;
	}

	@Bean
	@Lazy() //this bean is lazy because we want its creation to be delayed - forced as a part of context creation
	public ExecutorServiceManager executorServiceManager(ThreadLocalsSupport support,
														 LogDispatcher logDispatcher,
														 CamelContext context,
														 InterfaceRunRequest interfaceRunRequest,
														 CurrentRunIdHolder currentRunIdHolder,
														 AdditionalFieldHolderRegistry registry) {
		return new ExecutorServiceManager(context, support, logDispatcher, interfaceRunRequest, currentRunIdHolder, registry);
	}

	private String generateContextName(Interface anInterface, InboundMessage inboundMessage) {
		String interfaceName = anInterface.getName();
		String runId = getRunIdFromBeanFactory(inboundMessage);
		String camelContextName = interfaceName + " - " + runId;
		return camelContextName;
	}

	private String getRunIdFromBeanFactory(InboundMessage inboundMessage) {
		return inboundMessage.getProperty(Constants.RUN_ID);
	}


	@Bean
	public InterfacePropertiesToExchangeSetter interfacePropertiesToExchangeSetter(InterfacePropertiesCache interfacePropertiesCache, Interface anInterface) {
		return new InterfacePropertiesToExchangeSetter(interfacePropertiesCache, anInterface);
	}

	@Bean
	public PayloadValuesToExchangeSetter payloadValuesToExchangeSetter(InboundMessage inboundMessage) {
		return new PayloadValuesToExchangeSetter(inboundMessage);
	}

	@Bean
	public CamelPayloadAndInterfacePropertySetterListener camelPayloadAndInterfacePropertySetterListener(InterfacePropertiesToExchangeSetter interfacePropertiesToExchangeSetter,
																										 PayloadValuesToExchangeSetter payloadValuesToExchangeSetter) {
		return new CamelPayloadAndInterfacePropertySetterListener(interfacePropertiesToExchangeSetter, payloadValuesToExchangeSetter);
	}

	@Bean
	@Lazy(false)
	public GitLabKaravanSourceLoader gitLabKaravanSourceLoader(){
		return new GitLabKaravanSourceLoader();
	}

	 @Bean
	 public KaravanPropertySource karavanPropertySource(InterfaceJcaps interfaceJcaps,
														@Value("${config.UERuntimeEngine.ue.camel.karavan.karavanYamlPath:apps/ue/ueRelated/ueJars/karavan/}") String karavanYamlPath){
		 gitLabKaravanSourceLoader().loadFolder(interfaceJcaps.getName(),karavanYamlPath);
		return new KaravanPropertySource(interfaceJcaps, karavanYamlPath);
	 }

	@ConditionalOnClass(name = "com.mchange.v2.c3p0.ComboPooledDataSource")
	@Bean
	public DatasourceDynamicDefaultValueOverrider c3PoDynamicDefaultValueOverrider(Interface iface,
																				   LibraryVersionAware<UECamelBridgeJar> versionAware,
																				   @Value("${config.UERuntimeEngine.featureSwitch.MDW-2721:UECamelBridge:::21.45.R296}") String featureSwitchLibraryVersion) {
		return new DatasourceDynamicDefaultValueOverrider(iface,
														  versionAware,
														  RelatedJarsVersionsProvider.featureSwitchCondition(featureSwitchLibraryVersion)
																					 .getValue());
	}

	@Bean
	@ConditionalOnNonSpringTransactionHandling
	public TransactionNeededDetector transactionNeededDetector() {
		return new TransactionNeededDetector();
	}

	@Bean
	@ConditionalOnNonSpringTransactionHandling
	public ContextTransactionHandler contextTransactionHandler(TransactionNeededDetector neededDetector) {
		return new ContextTransactionHandler(neededDetector);
	}

	@OnArchetypeIntegrationTest
	@Bean
	public UECamelBridgeImplArchetypeIntegration ueCamelBridgeImplArchetypeIntegration() {
		return new UECamelBridgeImplArchetypeIntegration();
	}

	@ConditionalOnMissingBean
	@Bean
	public UECamelBridgeImpl ueCamelBridgeImpl() {
		return new UECamelBridgeImpl();
	}

	@Bean
	@Conditional(ConditionalOnMissingBeanAndNotJunit.class)
	public CamelRunIdGeneratorListener camelRunIdGeneratorListener(Interface anInterface) {
		if (InterfaceJcaps.class.isAssignableFrom(anInterface.getClass())) {
			InterfaceJcaps iface = (InterfaceJcaps) anInterface;
			if (iface.isServiceFlg()) {
				return new GenericCamelRunIdGenerator();
			} else {
				return new NoRunIdDummyGenerator();
			}

		} else {
			throw new RuntimeException("Dont know how to handle a non interfaceJcaps interface of class " + anInterface.getClass());
		}
	}

	@Bean
	public CamelArchiverListener archiverListener() {
		return new CamelArchiverListener();
	}

	@Bean
	public CamelShutdownListener camelShutdownListener(ErrorHandlerInterceptor errorHandlerInterceptor) {
		return new CamelShutdownListener(errorHandlerInterceptor);
	}

	@Bean
	public RestartService restartService(RuntimeEngineApi runtimeEngineApi, InterfaceJcaps interfaceJcaps) {
		return new RestartService(runtimeEngineApi, interfaceJcaps);
	}

	@Bean
	public InterfacePropertyParameterProvider interfacePropertyParameterProvider(InboundMessage inboundMessage) {
		return new InterfacePropertyParameterProvider(inboundMessage);
	}

	@Bean
	public MapBackedUpEndpointInjectFactory mapBackedUpEndpointInjectFactory() {
		return new MapBackedUpEndpointInjectFactory();
	}

	@Bean
	@ConditionalOnMissingBean
	public ParameterResolver parameterResolver(LogDispatcher logDispatcher, @Autowired(required = false) List<ParameterProvider> parameterProviderList) {
		return new ParameterResolver(logDispatcher, parameterProviderList);
	}

	@Bean
	public RunIdSetter runIdSetter(OutboundMessageFactory outboundMessageFactory) {
		return new RunIdSetter(outboundMessageFactory);
	}

	@Bean
	public ComponentFactory componentFactory(FactoryRouter router, ConnectionProvision connections) {
		return new ComponentFactory(router, connections);
	}

	@Bean
	@Scope(DefaultListableBeanFactory.SCOPE_PROTOTYPE)
	public RollbackProcessorAndDisable rollbackProcessorAndDisable(CamelContext context,
																   FailureHandledListener handledFailureListener,
																   RollbackProcessor rollbackProcessor) {
		return new RollbackProcessorAndDisable(context, handledFailureListener, rollbackProcessor);
	}

	@Bean
	public AllOrNothingRollback allOrNothingRollback(Optional<AllOrNothingTransactionManager> allOrNothingTransactionManager) {
		return new AllOrNothingRollback(allOrNothingTransactionManager);
	}

	@Bean
	public AllOrNothingCommit allOrNothingCommit(Optional<AllOrNothingTransactionManager> allOrNothingTransactionManager) {
		return new AllOrNothingCommit(allOrNothingTransactionManager);
	}

	@Bean
	public IntendedShutdown intendedShutdown(UECamelBridgeImpl camelBridge) {
		return new IntendedShutdown(camelBridge);
	}
