/*
 * Copyright 2023-present 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.data.elasticsearch.client.elc;

import static org.assertj.core.api.Assertions.*;

import co.elastic.clients.elasticsearch.core.UpdateRequest;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;

import java.util.List;

import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.DocValueField;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;

/**
 * @author Peter-Josef Meisch
 * @author Han Seungwoo
 */
class RequestConverterTest {

	private static final SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
	private static final MappingElasticsearchConverter converter = new MappingElasticsearchConverter(mappingContext);
	private final JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper();
	private final RequestConverter requestConverter = new RequestConverter(converter, jsonpMapper);

	@Test // #2316
	@DisplayName("should add docvalue_fields")
	void shouldAddDocvalueFields() {

		var docValueFields = List.of( //
				new DocValueField("field1"), //
				new DocValueField("field2", "format2") //
		);
		// doesn't matter what type of query is used, the relevant part for docvalue_fields is in the base builder.
		var query = StringQuery.builder("""
				{
					"match_all":{}
				}
				""") //
				.withDocValueFields(docValueFields) //
				.build();

		var searchRequest = requestConverter.searchRequest(query, null, SampleEntity.class, IndexCoordinates.of("foo"),
				true);

		var fieldAndFormats = searchRequest.docvalueFields();
		assertThat(fieldAndFormats).hasSize(2);
		assertThat(fieldAndFormats.get(0).field()).isEqualTo("field1");
		assertThat(fieldAndFormats.get(0).format()).isNull();
		assertThat(fieldAndFormats.get(1).field()).isEqualTo("field2");
		assertThat(fieldAndFormats.get(1).format()).isEqualTo("format2");
	}

	@Test // #2973
	@DisplayName("should set refresh based on deleteRequest")
	void refreshSetByDeleteRequest() {
		var query = new CriteriaQuery(new Criteria("text").contains("test"));
		var deleteQuery = DeleteQuery.builder(query).withRefresh(true).build();

		var deleteByQueryRequest = requestConverter.documentDeleteByQueryRequest(deleteQuery, null, SampleEntity.class,
				IndexCoordinates.of("foo"),
				null);

		assertThat(deleteByQueryRequest.refresh()).isTrue();
	}

	@Document(indexName = "does-not-matter")
	static class SampleEntity {
		@Nullable
		@Id private String id;
		@Nullable
		@Field(type = FieldType.Text) private String text;
	}

	@Test // #3231
	@DisplayName("should not use updatequery id for script id")
	void shouldNotUseUpdatequeryIdForScriptId() {
		var updateQuery = UpdateQuery
				.builder("queryId")
				.withScript("script")
				.build();

		UpdateRequest<org.springframework.data.elasticsearch.core.document.Document, ?> updateRequest = requestConverter
				.documentUpdateRequest(updateQuery, IndexCoordinates.of("foo"), null, null);

		assertThat(updateRequest.id()).isEqualTo("queryId");
		assertThat(updateRequest.script().source().scriptString()).isEqualTo("script");
		assertThat(updateRequest.script().id()).isNull();
	}

	@Test // #3231
	@DisplayName("should use script name as update query id")
	void shouldUseScriptNameAsUpdateQueryId() {
		var updateQuery = UpdateQuery
				.builder("queryId")
				.withScript("script")
				.withScriptName("scriptName")
				.build();

		UpdateRequest<org.springframework.data.elasticsearch.core.document.Document, ?> updateRequest = requestConverter
				.documentUpdateRequest(updateQuery, IndexCoordinates.of("foo"), null, null);

		assertThat(updateRequest.id()).isEqualTo("queryId");
		assertThat(updateRequest.script().source().scriptString()).isEqualTo("script");
		assertThat(updateRequest.script().id()).isEqualTo("scriptName");
	}
}
