package com.arms.egovframework.javaservice.esframework.config;

import com.arms.egovframework.javaservice.esframework.annotation.ElasticSearchTemplateConfig;
import com.arms.egovframework.javaservice.esframework.annotation.RollingIndexName;
import lombok.extern.slf4j.Slf4j;

import org.opensearch.client.RequestOptions;
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.client.indices.CreateIndexRequest;
import org.opensearch.client.indices.GetIndexRequest;
import org.opensearch.client.indices.GetIndexResponse;
import org.opensearch.cluster.metadata.AliasMetadata;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.index.AliasAction;
import org.springframework.data.elasticsearch.core.index.AliasActionParameters;
import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;

import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import jakarta.annotation.PostConstruct;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.arms.egovframework.javaservice.esframework.util.ReflectionUtil.findAnnotatedClasses;
import static com.arms.egovframework.javaservice.esframework.util.ReflectionUtil.methodInfo;

@Component
@Slf4j
public class EsIndexTemplateConfig {

	private final ElasticsearchOperations operations;

	private final RestHighLevelClient restHighLevelClient;

	public final String elasticsearchRepositoryPath;

	public EsIndexTemplateConfig(ElasticsearchOperations operations
			, RestHighLevelClient restHighLevelClient
			, @Value("${spring.elasticsearch-repository.path}")String elasticsearchRepositoryPath) {
		this.operations = operations;
		this.restHighLevelClient = restHighLevelClient;
		this.elasticsearchRepositoryPath = elasticsearchRepositoryPath;
	}

	@PostConstruct
	public void run() {

		Set<String> annotatedClasses
				= findAnnotatedClasses(ElasticSearchTemplateConfig.class, elasticsearchRepositoryPath);

		annotatedClasses.stream().map(clazz -> {
			try {
				return Class.forName(clazz);
			} catch (ClassNotFoundException e) {
				throw new IllegalArgumentException(e);
			}
		}).forEach(clazz->{
			Document document = AnnotationUtils.findAnnotation(clazz, Document.class);

			if(document!=null){

				IndexCoordinates indexCoordinatesFor = operations.getIndexCoordinatesFor(clazz);
				String indexName = indexCoordinatesFor.getIndexName();

				var indexOperations = operations.indexOps(clazz);

				createTemplate(indexName, indexOperations, clazz);
				createIndex(indexName, clazz);
				createAlias(indexName,  indexOperations);
			}
		});

	}

	private boolean isExistRollingIndexNameAnnotation(Class<?> clazz){
		try{
			methodInfo(clazz, RollingIndexName.class);
			return true;
		}catch (RuntimeException e){
			log.info("RollingIndexName 어노테이션이 없음 : {}",e.getMessage());
			return false;
		}catch (Exception e){
			return false;
		}
	}

	private void createTemplate(String indexName, IndexOperations indexOperations , Class<?> clazz) {

		var templateName = indexName+"-template";
		var templatePattern = indexName+"*";

		if(isExistRollingIndexNameAnnotation(clazz)){
			templatePattern = indexName+"-*";
		}

		if (!indexOperations.existsTemplate(templateName)) {
			log.info("template-{} 생성진행", templateName);

			var mapping = indexOperations.createMapping();

			PutTemplateRequest.TemplateRequestBuilder templateRequestBuilder = PutTemplateRequest.builder(templateName, templatePattern)
					.withMappings(mapping);

			if(isExistRollingIndexNameAnnotation(clazz)){
				templateRequestBuilder.withAliasActions(new AliasActions().add(
						new AliasAction.Add(AliasActionParameters.builderForTemplate()
								.withAliases(indexOperations.getIndexCoordinates().getIndexNames())
								.build())
				));
			}

			var request = templateRequestBuilder.build();

			try{
				indexOperations.putTemplate(request);
			}catch (RuntimeException e) {
				log.info("템플레이트 생성 패스 : {}",e.getMessage());
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}

	}

	private void createIndex(String indexName, Class<?> clazz) {

		try{
			Method method = methodInfo(clazz, RollingIndexName.class);
			Constructor<?> constructor = clazz.getConstructor();
			Object o = constructor.newInstance();

			if(isExistRollingIndexNameAnnotation(clazz)){
				indexName = indexName + "-" + method.invoke(o);
			}

			GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
			boolean exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
			if(!exists){
				CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
				restHighLevelClient.indices().create(createIndexRequest,RequestOptions.DEFAULT);
			}
		}catch (RuntimeException e) {
			log.info("인덱스 생성 패스 : {}",e.getMessage());
		} catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

	private void createAlias(String indexName, IndexOperations indexOperations) {

		try{
			GetIndexResponse getIndexResponse = restHighLevelClient.indices()
					.get(new GetIndexRequest(indexName+"*"), RequestOptions.DEFAULT);
			Map<String, List<AliasMetadata>> aliasesMap = getIndexResponse.getAliases();

			String[] isEmptyAliasIndex = aliasesMap.entrySet().stream()
					.filter(a -> a.getValue().isEmpty()).map(Map.Entry::getKey)
					.toArray(String[]::new);
			if(!ObjectUtils.isEmpty(isEmptyAliasIndex)){

				AliasActions add = new AliasActions().add(
						new AliasAction.Add(AliasActionParameters.builderForTemplate()
								.withAliases(indexOperations.getIndexCoordinates().getIndexNames())
								.withIndices(isEmptyAliasIndex)
								.build())
				);

				indexOperations.alias(add);

			}


		}catch (RuntimeException e){
			log.info("알리아스 생성 패스 : {}",e.getMessage());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

}
