package ru.ibs.socrates.confexporter.controller;


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.database.rider.core.api.configuration.DBUnit;
import com.github.database.rider.core.api.configuration.Orthography;
import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.junit5.api.DBRider;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.dbunit.ext.postgresql.PostgresqlDataTypeFactory;
import org.json.JSONException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.skyscreamer.jsonassert.Customization;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.comparator.CustomComparator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.NestedTestConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import ru.ibs.socrates.confexporter.api.dto.ProjectContent;
import ru.ibs.socrates.confexporter.api.dto.ProjectContentResponse;
import ru.ibs.socrates.confexporter.dao.repository.MessageRepository;
import ru.ibs.socrates.confexporter.dao.repository.ProjectRepository;
import ru.ibs.socrates.confexporter.dao.repository.TextContentRepository;
import ru.ibs.socrates.confexporter.external.pageparser.client.PageparserApiClient;
import ru.ibs.socrates.confexporter.external.sdtllmraggateconf.api.ContentApi;
import ru.ibs.socrates.confexporter.external.xmlparser.client.XmlparserApiClient;
import ru.ibs.socrates.confexporter.service.ProcessContentServiceImplTest;


@Slf4j
@ActiveProfiles({"disable-feign-client", "test"})
@NestedTestConfiguration(NestedTestConfiguration.EnclosingConfiguration.INHERIT)
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
@DBRider
@DBUnit(allowEmptyFields = true, cacheConnection = false, columnSensing = true,
    caseSensitiveTableNames = false, caseInsensitiveStrategy = Orthography.LOWERCASE,
    mergeDataSets = true,
    dataTypeFactoryClass = PostgresqlDataTypeFactory.class)
public class ProjectContentFromDateControllerTest {

  @Container
  private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
      .withDatabaseName("testdb")
      .withUsername("test")
      .withPassword("test");

  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.password", postgres::getPassword);
    registry.add("spring.datasource.username", postgres::getUsername);
  }


  @Autowired
  protected MockMvc mockMvc;

  @Autowired
  protected MessageRepository contentRepository;

  @Autowired
  protected TextContentRepository textContentRepository;

  @Autowired
  protected ProjectRepository projectRepository;

  @Autowired
  private ObjectMapper objectMapper;

  @Autowired
  private ContentApi contentApi;

  @Autowired
  private PageparserApiClient pageparserApiClient;

  @Autowired
  private XmlparserApiClient xmlparserApiClient;

  @BeforeEach
  void init() {
    Mockito.reset(contentApi, pageparserApiClient, xmlparserApiClient);
    projectRepository.deleteAll();
    contentRepository.deleteAll();
    textContentRepository.deleteAll();
  }

  @DisplayName("001 test empty")
  @Test
  @DataSet(value = {
      "data/project-content/test-001/project.xml",
      "data/project-content/test-001/message.xml",
      "data/project-content/test-001/text_content.xml",
  })
  void test001() throws Exception {

    String testResourceFolder = "/data/project-content/test-001";

    MvcResult mvcResult = mockMvc.perform(
        get("/project/00000000-0000-0000-0000-000000000000/content/2000-10-31T01:30:00.000-05:00")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isNotFound())
        .andReturn();

    Assertions.assertEquals("", mvcResult.getResponse().getContentAsString());
  }


  @DisplayName("002 test ok")
  @Test
  @DataSet(value = {
      "data/project-content/test-001/project.xml",
      "data/project-content/test-001/message.xml",
      "data/project-content/test-001/text_content.xml",
  })
  void test002() throws Exception {

    String testResourceFolder = "/data/project-content/test-002";

    MvcResult mvcResult = mockMvc.perform(
        get("/project/58d07521-9659-4a80-b03d-667479c2b123/content/2000-10-31T01:30:00.000-05:00")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andReturn();

    assertJson(testResourceFolder + "/response.json",
        "**.id").accept(sortContent(mvcResult));
  }


  @DisplayName("003 test 10 items")
  @Test
  @DataSet(value = {
      "data/project-content/test-003/project.xml",
      "data/project-content/test-003/message.xml",
      "data/project-content/test-003/text_content.xml",
  })
  void test003() throws Exception {

    String testResourceFolder = "/data/project-content/test-003";

    MvcResult mvcResult = mockMvc.perform(
        get("/project/58d07521-9659-4a80-b03d-667479c2b123/content/2000-10-31T01:30:00.000-05:00")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andReturn();

    assertJson(testResourceFolder + "/response.json",
        "**.id").accept(sortContent(mvcResult));
  }
  

  @DisplayName("004 test messages limit")
  @Test
  @DataSet(value = {
      "data/project-content/test-003/project.xml",
      "data/project-content/test-003/message.xml",
      "data/project-content/test-003/text_content.xml",
      "data/project-content/test-004/message.xml",
      "data/project-content/test-004/text_content.xml",
  })
  void test004() throws Exception {

    String testResourceFolder = "/data/project-content/test-004";

    MvcResult mvcResult = mockMvc.perform(
        get("/project/58d07521-9659-4a80-b03d-667479c2b123/content/2000-10-31T01:30:00.000-05:00")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andReturn();

    assertJson(testResourceFolder + "/response.json",
        "**.id").accept(sortContent(mvcResult));
  }
  

  @DisplayName("005 test content limit with xml")
  @Test
  @DataSet(value = {
      "data/project-content/test-003/project.xml",
      "data/project-content/test-003/message.xml",
      "data/project-content/test-003/text_content.xml",
      "data/project-content/test-005/message.xml",
      "data/project-content/test-005/text_content.xml",
  })
  void test005() throws Exception {

    String testResourceFolder = "/data/project-content/test-005";

    MvcResult mvcResult = mockMvc.perform(
        get("/project/58d07521-9659-4a80-b03d-667479c2b123/content/2000-10-31T01:30:00.000-05:00")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andReturn();

    assertJson(testResourceFolder + "/response.json",
        "**.id").accept(sortContent(mvcResult));
  }
  

  @DisplayName("006 test content limitt with page")
  @Test
  @DataSet(value = {
      "data/project-content/test-003/project.xml",
      "data/project-content/test-003/message.xml",
      "data/project-content/test-003/text_content.xml",
      "data/project-content/test-006/message.xml",
      "data/project-content/test-006/text_content.xml",
  })
  void test006() throws Exception {

    String testResourceFolder = "/data/project-content/test-006";

    MvcResult mvcResult = mockMvc.perform(
        get("/project/58d07521-9659-4a80-b03d-667479c2b123/content/2000-10-31T01:30:00.000-05:00")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andReturn();

    assertJson(testResourceFolder + "/response.json",
        "**.id").accept(sortContent(mvcResult));
  }


  @DisplayName("007 test content limitt with page from-date")
  @Test
  @DataSet(value = {
      "data/project-content/test-003/project.xml",
      "data/project-content/test-003/message.xml",
      "data/project-content/test-003/text_content.xml",
      "data/project-content/test-007/message.xml",
      "data/project-content/test-007/text_content.xml",
  })
  void test007() throws Exception {

    String testResourceFolder = "/data/project-content/test-007";

    MvcResult mvcResult = mockMvc.perform(
        get("/project/58d07521-9659-4a80-b03d-667479c2b123/content/2023-06-15T01:30:00.000-05:00")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andReturn();

    assertJson(testResourceFolder + "/response.json",
        "**.id").accept(sortContent(mvcResult));
  }


  protected <T> Consumer<T> assertJson(String filePathExpected, String... skipField) {
    String expectedJson = getFileData(filePathExpected);
    Consumer<T> func = (obj) -> {
      String jsonActual;
      try {
        if (obj instanceof String) {
          jsonActual = (String) obj;
        } else if (obj instanceof byte[]) {
          jsonActual = new String((byte[]) obj, StandardCharsets.UTF_8);
        } else if (obj instanceof MvcResult) {
          jsonActual = getMvcResultJson((MvcResult) obj);
        } else {
          jsonActual = toJson(obj);
        }
        Customization[] customizations = Arrays.stream(skipField)
            .map(field -> new Customization(field, (a, b) -> true))
            .toArray(Customization[]::new);
        JSONAssert.assertEquals("filepath = " + filePathExpected
            + ", actual json = " + jsonActual + ", ->",
            expectedJson,
            jsonActual,
            new CustomComparator(JSONCompareMode.STRICT_ORDER,
                customizations));
      } catch (JSONException | UnsupportedEncodingException e) {
        throw (RuntimeException) ExceptionUtils.rethrow(e);
      }
    };
    return func;
  }
  
  

  public <T> T getResource(String resourceName, Class<T> clazz) {
    T res = null;
    try (Reader json = new InputStreamReader(
        ProcessContentServiceImplTest.class.getResourceAsStream(resourceName))) {
      res = objectMapper.readValue(json, clazz);
    } catch (Exception e) {
      log.error("unable to get resource {}", resourceName);
      Assertions.assertTrue(false, "unable to get resource " + resourceName);
    }
    return res;
  }

  protected String getFileData(String filePath) {
    try (InputStream inputStream = this.getClass().getResourceAsStream(filePath)) {
      return new BufferedReader(new InputStreamReader(inputStream))
          .lines()
          .collect(Collectors.joining("\n"));
    } catch (Exception e) {
      Assertions.assertEquals("", filePath, "filepath = " + filePath + " not found");
      throw (RuntimeException) ExceptionUtils.rethrow(e);
    }
  }

  protected String getMvcResultJson(MvcResult mvcResult) throws UnsupportedEncodingException {
    return mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
  }

  @SneakyThrows
  public String toJson(Object body) {
    if (Objects.isNull(body)) {
      return StringUtils.EMPTY;
    }
    return objectMapper.writeValueAsString(body);
  }
  
  private ProjectContentResponse sortContent(MvcResult mvcResult)
      throws JsonMappingException, JsonProcessingException, UnsupportedEncodingException {
    ProjectContentResponse response =
        objectMapper.readValue(mvcResult.getResponse().getContentAsString(),
            new TypeReference<ProjectContentResponse>() {});
    response.setContents(response.getContents().stream()
        .sorted(Comparator.comparing(ProjectContent::getUpdated,
            Comparator.nullsLast(Comparator.reverseOrder()))).toList());
    return response;
  }
}
