/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed 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
 *
 *      https://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.springframework.cloud.appbroker.autoconfigure;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.appbroker.deployer.AppDeployer;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
import org.springframework.cloud.appbroker.deployer.BackingService;
import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.deployer.DefaultBackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.DefaultBackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.DeployerClient;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialGenerator;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderFactory;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.credentials.SimpleCredentialGenerator;
import org.springframework.cloud.appbroker.extensions.credentials.SpringSecurityBasicAuthCredentialProviderFactory;
import org.springframework.cloud.appbroker.extensions.credentials.SpringSecurityOAuth2CredentialProviderFactory;
import org.springframework.cloud.appbroker.extensions.parameters.BackingApplicationsParametersTransformationService;
import org.springframework.cloud.appbroker.extensions.parameters.BackingServicesParametersTransformationService;
import org.springframework.cloud.appbroker.extensions.parameters.EnvironmentMappingParametersTransformerFactory;
import org.springframework.cloud.appbroker.extensions.parameters.ParameterMappingParametersTransformerFactory;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformerFactory;
import org.springframework.cloud.appbroker.extensions.parameters.PropertyMappingParametersTransformerFactory;
import org.springframework.cloud.appbroker.extensions.targets.ServiceInstanceGuidSuffix;
import org.springframework.cloud.appbroker.extensions.targets.SpacePerServiceInstance;
import org.springframework.cloud.appbroker.extensions.targets.TargetFactory;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.manager.AppManager;
import org.springframework.cloud.appbroker.manager.BackingAppManagementService;
import org.springframework.cloud.appbroker.manager.ManagementClient;
import org.springframework.cloud.appbroker.oauth2.OAuth2Client;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceAppBindingWorkflow;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceRouteBindingWorkflow;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.DeleteServiceInstanceBindingWorkflow;
import org.springframework.cloud.appbroker.service.DeleteServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.UpdateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.WorkflowServiceInstanceBindingService;
import org.springframework.cloud.appbroker.service.WorkflowServiceInstanceService;
import org.springframework.cloud.appbroker.state.InMemoryServiceInstanceBindingStateRepository;
import org.springframework.cloud.appbroker.state.InMemoryServiceInstanceStateRepository;
import org.springframework.cloud.appbroker.state.ServiceInstanceBindingStateRepository;
import org.springframework.cloud.appbroker.state.ServiceInstanceStateRepository;
import org.springframework.cloud.appbroker.workflow.instance.AppDeploymentCreateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.workflow.instance.AppDeploymentDeleteServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.workflow.instance.AppDeploymentUpdateServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.service.ServiceInstanceBindingService;
import org.springframework.cloud.servicebroker.service.ServiceInstanceService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * App Broker Auto-configuration
 */
@SuppressWarnings("PMD.CouplingBetweenObjects")
@Configuration
@AutoConfigureAfter(CloudFoundryAppDeployerAutoConfiguration.class)
@ConditionalOnBean(AppDeployer.class)
public class AppBrokerAutoConfiguration {

	private static final Logger LOG = LoggerFactory.getLogger(AppBrokerAutoConfiguration.class);

	private static final String PROPERTY_PREFIX = "spring.cloud.appbroker";

	/**
	 * Provide a {@link DeployerClient} bean
	 *
	 * @param appDeployer the AppDeployer bean
	 * @return the bean
	 */
	@Bean
	public DeployerClient deployerClient(AppDeployer appDeployer) {
		return new DeployerClient(appDeployer);
	}

	/**
	 * Provide a {@link BackingAppDeploymentService} bean
	 *
	 * @param deployerClient the DeployerClient bean
	 * @return the bean
	 */
	@Bean
	@ConditionalOnMissingBean
	public BackingAppDeploymentService backingAppDeploymentService(DeployerClient deployerClient) {
		return new DefaultBackingAppDeploymentService(deployerClient);
	}

	/**
	 * Provide a {@link ManagementClient} bean
	 *
	 * @param appManager the AppManager bean
	 * @return the bean
	 */
	@Bean
	public ManagementClient managementClient(AppManager appManager) {
		return new ManagementClient(appManager);
	}

	/**
	 * Provide a {@link BackingAppManagementService} bean
	 *
	 * @param managementClient the ManagementClient bean
	 * @param appDeployer the AppDeployer bean
	 * @param brokeredServices the BrokeredServices bean
	 * @param targetService the TargetService bean
	 * @return the bean
	 */
	@Bean
	public BackingAppManagementService backingAppManagementService(ManagementClient managementClient,
		AppDeployer appDeployer, BrokeredServices brokeredServices, TargetService targetService) {
		return new BackingAppManagementService(managementClient, appDeployer, brokeredServices, targetService);
	}

	/**
	 * Provide a {@link BrokeredServices} bean
	 *
	 * @return the bean
	 */
	@Bean
	@ConfigurationProperties(PROPERTY_PREFIX + ".services")
	@ConditionalOnMissingBean
	public BrokeredServices brokeredServices() {
		return BrokeredServices.builder().build();
	}

	/**
	 * Provide a {@link ServiceInstanceStateRepository} bean
	 *
	 * @return the bean
	 */
	@Bean
	@ConditionalOnMissingBean(ServiceInstanceStateRepository.class)
	public ServiceInstanceStateRepository serviceInstanceStateRepository() {
		if (LOG.isWarnEnabled()) {
			LOG.warn("The InMemoryServiceInstanceStateRepository is provided for demonstration and testing purposes " +
					"only. It is not suitable for production applications!");
		}
		return new InMemoryServiceInstanceStateRepository();
	}

	/**
	 * Provide a {@link ServiceInstanceBindingStateRepository} bean
	 *
	 * @return the bean
	 */
	@Bean
	@ConditionalOnMissingBean(ServiceInstanceBindingStateRepository.class)
	public ServiceInstanceBindingStateRepository serviceInstanceBindingStateRepository() {
		if (LOG.isWarnEnabled()) {
			LOG.warn("The InMemoryServiceInstanceBindingStateRepository is provided for demonstration and testing " +
					"purposes only. It is not suitable for production applications!");
		}
		return new InMemoryServiceInstanceBindingStateRepository();
	}

	/**
	 * Provide an {@link EnvironmentMappingParametersTransformerFactory} bean
	 *
	 * @return the bean
	 */
	@Bean
	public EnvironmentMappingParametersTransformerFactory environmentMappingParametersTransformerFactory() {
		return new EnvironmentMappingParametersTransformerFactory();
	}

	/**
	 * Provide a {@link ParameterMappingParametersTransformerFactory} bean
	 *
	 * @return the bean
	 */
	@Bean
	public PropertyMappingParametersTransformerFactory propertyMappingParametersTransformerFactory() {
		return new PropertyMappingParametersTransformerFactory();
	}

	/**
	 * Provide a {@link ParameterMappingParametersTransformerFactory} bean
	 *
	 * @return the bean
	 */
	@Bean
	public ParameterMappingParametersTransformerFactory parameterMappingParametersTransformerFactory() {
		return new ParameterMappingParametersTransformerFactory();
	}

	/**
	 * Provide a {@link BackingApplicationsParametersTransformationService} bean
	 *
	 * @param transformers a collection of parameter transformers
	 * @return the bean
	 */
	@Bean
	public BackingApplicationsParametersTransformationService backingApplicationsParametersTransformationService(
		List<ParametersTransformerFactory<BackingApplication, ?>> transformers) {
		return new BackingApplicationsParametersTransformationService(transformers);
	}

	/**
	 * Provide a {@link BackingServicesParametersTransformationService} bean
	 *
	 * @param transformers a collection of parameter transformers
	 * @return the bean
	 */
	@Bean
	public BackingServicesParametersTransformationService backingServicesParametersTransformationService(
		List<ParametersTransformerFactory<BackingService, ?>> transformers) {
		return new BackingServicesParametersTransformationService(transformers);
	}

	/**
	 * Provide a {@link SimpleCredentialGenerator} bean
	 *
	 * @return the bean
	 */
	@ConditionalOnMissingBean(CredentialGenerator.class)
	@Bean
	public SimpleCredentialGenerator simpleCredentialGenerator() {
		return new SimpleCredentialGenerator();
	}

	/**
	 * Provide a {@link SpringSecurityBasicAuthCredentialProviderFactory} bean
	 *
	 * @param credentialGenerator the CredentialGenerator bean
	 * @return the bean
	 */
	@Bean
	public SpringSecurityBasicAuthCredentialProviderFactory springSecurityBasicAuthCredentialProvider(
		CredentialGenerator credentialGenerator) {
		return new SpringSecurityBasicAuthCredentialProviderFactory(credentialGenerator);
	}

	/**
	 * Provide a {@link SpringSecurityOAuth2CredentialProviderFactory} bean
	 *
	 * @param credentialGenerator the CredentialGenerator bean
	 * @param oAuth2Client the OAuth2Client bean
	 * @return the bean
	 */
	@Bean
	public SpringSecurityOAuth2CredentialProviderFactory springSecurityOAuth2CredentialProvider(
		CredentialGenerator credentialGenerator,
		OAuth2Client oAuth2Client) {
		return new SpringSecurityOAuth2CredentialProviderFactory(credentialGenerator, oAuth2Client);
	}

	/**
	 * Provide a {@link CredentialProviderService} bean
	 *
	 * @param providers a collection of credential providers
	 * @return the bean
	 */
	@Bean
	public CredentialProviderService credentialProviderService(List<CredentialProviderFactory<?>> providers) {
		return new CredentialProviderService(providers);
	}

	/**
	 * Provide a {@link SpacePerServiceInstance} bean
	 *
	 * @return the bean
	 */
	@Bean
	public SpacePerServiceInstance spacePerServiceInstance() {
		return new SpacePerServiceInstance();
	}

	/**
	 * Provide a {@link ServiceInstanceGuidSuffix} bean
	 *
	 * @return the bean
	 */
	@Bean
	public ServiceInstanceGuidSuffix serviceInstanceGuidSuffix() {
		return new ServiceInstanceGuidSuffix();
	}

	/**
	 * Provide a {@link TargetService} bean
	 *
	 * @param targets a collection of targets
	 * @return the bean
	 */
	@Bean
	public TargetService targetService(List<TargetFactory<?>> targets) {
		return new TargetService(targets);
	}

	/**
	 * Provide a {@link BackingServicesProvisionService} bean
	 *
	 * @param deployerClient the DeployerClient bean
	 * @return the bean
	 */
	@Bean
	@ConditionalOnMissingBean
	public BackingServicesProvisionService backingServicesProvisionService(DeployerClient deployerClient) {
		return new DefaultBackingServicesProvisionService(deployerClient);
	}

	/**
	 * Provide a {@link CreateServiceInstanceWorkflow} bean
	 *
	 * @param brokeredServices the BrokeredServices bean
	 * @param backingAppDeploymentService the BackingAppDeploymentService bean
	 * @param appsParametersTransformationService the BackingApplicationsParametersTransformationService bean
	 * @param servicesParametersTransformationService the BackingServicesParametersTransformationService bean
	 * @param credentialProviderService the CredentialProviderService bean
	 * @param targetService the TargetService bean
	 * @param backingServicesProvisionService the BackingServicesProvisionService bean
	 * @return the bean
	 */
	@Bean
	public CreateServiceInstanceWorkflow appDeploymentCreateServiceInstanceWorkflow(
		BrokeredServices brokeredServices, BackingAppDeploymentService backingAppDeploymentService,
		BackingApplicationsParametersTransformationService appsParametersTransformationService,
		BackingServicesParametersTransformationService servicesParametersTransformationService,
		CredentialProviderService credentialProviderService, TargetService targetService,
		BackingServicesProvisionService backingServicesProvisionService) {
		return new AppDeploymentCreateServiceInstanceWorkflow(
			brokeredServices,
			backingAppDeploymentService,
			backingServicesProvisionService,
			appsParametersTransformationService,
			servicesParametersTransformationService,
			credentialProviderService,
			targetService);
	}

	/**
	 * Provide a {@link UpdateServiceInstanceWorkflow} bean
	 *
	 * @param brokeredServices the BrokeredServices bean
	 * @param backingAppDeploymentService the BackingAppDeploymentService bean
	 * @param backingAppManagementService the BackingAppManagementService bean
	 * @param backingServicesProvisionService the BackingServicesProvisionService bean
	 * @param appsParametersTransformationService the BackingApplicationsParametersTransformationService bean
	 * @param servicesParametersTransformationService the BackingServicesParametersTransformationService bean
	 * @param targetService the TargetService bean
	 * @return the bean
	 */
	@Bean
	public UpdateServiceInstanceWorkflow appDeploymentUpdateServiceInstanceWorkflow(
		BrokeredServices brokeredServices,
		BackingAppDeploymentService backingAppDeploymentService,
		BackingAppManagementService backingAppManagementService,
		BackingServicesProvisionService backingServicesProvisionService,
		BackingApplicationsParametersTransformationService appsParametersTransformationService,
		BackingServicesParametersTransformationService servicesParametersTransformationService,
		TargetService targetService) {

		return new AppDeploymentUpdateServiceInstanceWorkflow(
			brokeredServices,
			backingAppDeploymentService,
			backingAppManagementService,
			backingServicesProvisionService,
			appsParametersTransformationService,
			servicesParametersTransformationService,
			targetService);
	}

	/**
	 * Provide a {@link DeleteServiceInstanceWorkflow} bean
	 *
	 * @param brokeredServices the BrokeredServices bean
	 * @param backingAppDeploymentService the BackingAppDeploymentService bean
	 * @param backingAppManagementService the BackingAppManagementService bean
	 * @param backingServicesProvisionService the BackingServicesProvisionService bean
	 * @param credentialProviderService the CredentialProviderService bean
	 * @param targetService the TargetService bean
	 * @return the bean
	 */
	@Bean
	public DeleteServiceInstanceWorkflow appDeploymentDeleteServiceInstanceWorkflow(
		BrokeredServices brokeredServices, BackingAppDeploymentService backingAppDeploymentService,
		BackingAppManagementService backingAppManagementService,
		BackingServicesProvisionService backingServicesProvisionService,
		CredentialProviderService credentialProviderService, TargetService targetService) {

		return new AppDeploymentDeleteServiceInstanceWorkflow(
			brokeredServices,
			backingAppDeploymentService,
			backingAppManagementService,
			backingServicesProvisionService,
			credentialProviderService,
			targetService
		);
	}

	/**
	 * Provide a {@link WorkflowServiceInstanceService} bean
	 *
	 * @param stateRepository the ServiceInstanceStateRepository bean
	 * @param createWorkflows a collection of create workflows
	 * @param deleteWorkflows a collection of delete workflows
	 * @param updateWorkflows a collection of update workflows
	 * @return the bean
	 */
	@Bean
	@ConditionalOnMissingBean(ServiceInstanceService.class)
	public WorkflowServiceInstanceService serviceInstanceService(ServiceInstanceStateRepository stateRepository,
		List<CreateServiceInstanceWorkflow> createWorkflows, List<DeleteServiceInstanceWorkflow> deleteWorkflows,
		List<UpdateServiceInstanceWorkflow> updateWorkflows) {
		return new WorkflowServiceInstanceService(stateRepository, createWorkflows, deleteWorkflows, updateWorkflows);
	}

	/**
	 * Provide a {@link WorkflowServiceInstanceBindingService} bean
	 *
	 * @param stateRepository the ServiceInstanceBindingStateRepository bean
	 * @param createServiceInstanceAppBindingWorkflows a collection of create app binding workflows
	 * @param createServiceInstanceRouteBindingWorkflows a collection of create route binding workflows
	 * @param deleteServiceInstanceBindingWorkflows a collection of update workflows
	 * @return the bean
	 */
	@Bean
	@ConditionalOnMissingBean(ServiceInstanceBindingService.class)
	public WorkflowServiceInstanceBindingService serviceInstanceBindingService(
		ServiceInstanceBindingStateRepository stateRepository,
		@Autowired(required = false) List<CreateServiceInstanceAppBindingWorkflow> createServiceInstanceAppBindingWorkflows,
		@Autowired(required = false) List<CreateServiceInstanceRouteBindingWorkflow> createServiceInstanceRouteBindingWorkflows,
		@Autowired(required = false) List<DeleteServiceInstanceBindingWorkflow> deleteServiceInstanceBindingWorkflows) {
		return new WorkflowServiceInstanceBindingService(stateRepository,
			createServiceInstanceAppBindingWorkflows, createServiceInstanceRouteBindingWorkflows,
			deleteServiceInstanceBindingWorkflows);
	}

}