/**
 * This file is part of Eclipse Steady.
 *
 * 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
 *
 *     http://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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.backend.rest;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;

import javax.persistence.EntityNotFoundException;

import org.eclipse.steady.backend.model.Space;
import org.eclipse.steady.backend.model.Tenant;
import org.eclipse.steady.backend.repo.SpaceRepository;
import org.eclipse.steady.backend.repo.TenantRepository;
import org.eclipse.steady.shared.enums.ExportConfiguration;
import org.eclipse.steady.shared.enums.PropertySource;
import org.eclipse.steady.shared.json.JacksonUtil;
import org.eclipse.steady.shared.util.Constants;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest(
    classes = MainController.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class SpaceControllerTest {

  private MediaType contentType =
      new MediaType(
          MediaType.APPLICATION_JSON.getType(),
          MediaType.APPLICATION_JSON.getSubtype(),
          Charset.forName("utf8"));

  private MockMvc mockMvc;
  private HttpMessageConverter<?> mappingJackson2HttpMessageConverter;

  public static final String TEST_DEFAULT_SPACE = "public";
  public static final String TEST_DEFAULT_TENANT = "default";

  @Autowired private SpaceRepository spaceRepository;

  @Autowired private TenantRepository tenantRepository;

  @Autowired private WebApplicationContext webApplicationContext;

  @Autowired
  void setConverters(HttpMessageConverter<?>[] converters) {
    this.mappingJackson2HttpMessageConverter =
        Arrays.asList(converters).stream()
            .filter(
                new Predicate<HttpMessageConverter<?>>() {
                  @Override
                  public boolean test(HttpMessageConverter<?> hmc) {
                    return hmc instanceof MappingJackson2HttpMessageConverter;
                  }
                })
            .findAny()
            .get();

    Assert.assertNotNull(
        "the JSON message converter must not be null", this.mappingJackson2HttpMessageConverter);
  }

  @Before
  public void setup() throws Exception {
    this.mockMvc = webAppContextSetup(webApplicationContext).build();
    this.spaceRepository.deleteAll();
    this.tenantRepository.deleteAll();
  }

  @After
  public void reset() throws Exception {
    this.spaceRepository.deleteAll();
    this.tenantRepository.deleteAll();
  }

  //	/**
  //	 * Check space creation without tenant header
  //	 * @throws Exception
  //	 */
  //	@Test
  //	public void testSpaceCreationNoTenant() throws Exception {
  //		final Tenant d_tenant = this.createDefaultTenant();
  //		com.sap.psr.vulas.shared.json.model.Space new_shared_space = new
  // com.sap.psr.vulas.shared.json.model.Space();
  //		new_shared_space.setSpaceName("spaceNoTenant");
  //		new_shared_space.setDefault(false);
  //		new_shared_space.setExportConfiguration(ExportConfiguration.OFF);
  //		new_shared_space.setSpaceDescription("description");
  //		new_shared_space.setOwnerEmails(new HashSet<String>(Arrays.asList(new String[]
  // {"foo@bar.com"})));
  //
  //
  //		// Post w/o tenant header, returns 400
  //		MockHttpServletRequestBuilder post_builder = post("/spaces")
  //				.content(JacksonUtil.asJsonString(new_shared_space))
  //				.contentType(MediaType.APPLICATION_JSON)
  //				.accept(MediaType.APPLICATION_JSON);
  //		mockMvc.perform(post_builder).andExpect(status().isBadRequest());
  //
  //		post_builder = post("/spaces")
  //				.header(Constants.HTTP_TENANT_HEADER,  d_tenant.getTenantToken())
  //				.content(JacksonUtil.asJsonString(new_shared_space))
  //				.contentType(MediaType.APPLICATION_JSON)
  //				.accept(MediaType.APPLICATION_JSON);
  //		mockMvc.perform(post_builder).andExpect(status().isCreated())
  //		.andExpect(content().contentType(contentType))
  //		.andExpect(jsonPath("$.spaceName", is(space_name)));
  //	}

  /**
   * Check space creation.
   *
   * @throws Exception
   */
  @Test
  public void testSpaceCreation() throws Exception {
    assertEquals(0, this.spaceRepository.count());
    final Tenant d_tenant = this.createDefaultTenant();
    final Space d_space = this.createSpace(d_tenant, TEST_DEFAULT_SPACE, true);
    assertEquals(1, this.spaceRepository.count());

    // Create 2nd space used for some tests
    final String space_name = "foo";
    org.eclipse.steady.shared.json.model.Space new_shared_space =
        new org.eclipse.steady.shared.json.model.Space();
    new_shared_space.setSpaceName(TEST_DEFAULT_SPACE);
    new_shared_space.setDefault(
        true); // This will not be possible, as another default space has been created above
    new_shared_space.setExportConfiguration(ExportConfiguration.OFF);
    new_shared_space.setSpaceDescription("bar");
    new_shared_space.setOwnerEmails(
        new HashSet<String>(Arrays.asList(new String[] {"foo@bar.com"})));

    // returns 400 as default space already exists
    MockHttpServletRequestBuilder post_builder =
        post("/spaces")
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON);
    mockMvc.perform(post_builder).andExpect(status().isBadRequest());
    //		MvcResult result = mockMvc.perform(post_builder).andExpect(status().isCreated())
    //				.andExpect(content().contentType(contentType))
    //				.andExpect(jsonPath("$.spaceName", is(space_name))).andReturn();

    // Still one space only
    assertEquals(1, this.spaceRepository.count());

    new_shared_space.setDefault(false);

    // Post w/o tenant header to try to create a NON default space
    post_builder =
        post("/spaces")
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON);
    mockMvc.perform(post_builder).andExpect(status().isCreated());

    assertEquals(2, this.spaceRepository.count());

    // Create with read-only enabled -> bad request
    new_shared_space.setReadOnly(true);
    post_builder =
        post("/spaces")
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON);
    mockMvc.perform(post_builder).andExpect(status().isBadRequest());

    // Still two space only
    assertEquals(2, this.spaceRepository.count());

    new_shared_space.setReadOnly(false);
    new_shared_space.setSpaceName(space_name);
    new_shared_space.setDefault(false);

    post_builder =
        post("/spaces")
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON);
    mockMvc
        .perform(post_builder)
        .andExpect(status().isCreated())
        .andExpect(content().contentType(contentType))
        .andExpect(jsonPath("$.spaceName", is(space_name)));

    // Now three space
    assertEquals(3, this.spaceRepository.count());

    // Post with non-existing tenant token, defaults in controller method
    MockHttpServletRequestBuilder post_builder2 =
        post("/spaces")
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .header(Constants.HTTP_TENANT_HEADER, "does-not-exist")
            .accept(MediaType.APPLICATION_JSON);
    mockMvc.perform(post_builder2).andExpect(status().isNotFound());
  }

  /**
   * Check presence of spaces.
   *
   * @throws Exception
   */
  @Test
  public void testGetSpace() throws Exception {
    assertEquals(0, this.spaceRepository.count());
    final Tenant d_tenant = this.createDefaultTenant();
    final Space d_space = this.createSpace(d_tenant, TEST_DEFAULT_SPACE, true);
    assertEquals(1, this.spaceRepository.count());

    // Default tenant exists
    MockHttpServletRequestBuilder opts_builder =
        options("/spaces/" + TEST_DEFAULT_SPACE)
            .header(Constants.HTTP_TENANT_HEADER, TEST_DEFAULT_TENANT);
    mockMvc.perform(opts_builder).andExpect(status().isOk());

    // Default tenant members
    MockHttpServletRequestBuilder get_builder =
        get("/spaces/" + TEST_DEFAULT_SPACE)
            .header(Constants.HTTP_TENANT_HEADER, TEST_DEFAULT_TENANT)
            .accept(MediaType.APPLICATION_JSON);
    mockMvc
        .perform(get_builder)
        .andExpect(status().isOk())
        .andExpect(content().contentType(contentType))
        .andExpect(jsonPath("$.spaceToken", is(TEST_DEFAULT_SPACE)))
        .andExpect(jsonPath("$.spaceName", is(TEST_DEFAULT_SPACE)));
  }

  /**
   * Check space creation and deletion. <<<<<<< HEAD =======
   *
   * <p>>>>>>>> master
   *
   * @throws Exception
   */
  @Test
  public void testSpaceCreationAndDeletion() throws Exception {
    assertEquals(0, this.spaceRepository.count());
    final Tenant d_tenant = this.createDefaultTenant();
    final Space d_space = this.createSpace(d_tenant, TEST_DEFAULT_SPACE, true);
    assertEquals(1, this.spaceRepository.count());

    final String space_name = "foo";
    org.eclipse.steady.shared.json.model.Space new_shared_space =
        new org.eclipse.steady.shared.json.model.Space();
    new_shared_space.setSpaceName("foo");
    new_shared_space.setExportConfiguration(ExportConfiguration.OFF);
    new_shared_space.setSpaceDescription("bar");
    new_shared_space.setOwnerEmails(
        new HashSet<String>(Arrays.asList(new String[] {"foo@bar.com"})));

    // Post w tenant header (should create a NON default space as it's not provided)
    MockHttpServletRequestBuilder post_builder =
        post("/spaces")
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    MvcResult result =
        mockMvc
            .perform(post_builder)
            .andExpect(status().isCreated())
            .andExpect(content().contentType(contentType))
            .andExpect(jsonPath("$.default", is(false)))
            .andExpect(jsonPath("$.spaceName", is(space_name)))
            .andReturn();

    assertEquals(2, this.spaceRepository.count());

    // Get the generated token
    org.eclipse.steady.shared.json.model.Space created_shared_space =
        (org.eclipse.steady.shared.json.model.Space)
            JacksonUtil.asObject(
                result.getResponse().getContentAsString(),
                org.eclipse.steady.shared.json.model.Space.class);

    // Post with non-existing tenant token, defaults in controller method
    MockHttpServletRequestBuilder del_builder =
        delete("/spaces/" + created_shared_space.getSpaceToken())
            .header(Constants.HTTP_TENANT_HEADER, TEST_DEFAULT_TENANT)
            .accept(MediaType.APPLICATION_JSON);
    mockMvc.perform(del_builder).andExpect(status().isOk());

    assertEquals(1, this.spaceRepository.count());
  }

  /**
   * Test modification of read-only space. <<<<<<< HEAD =======
   *
   * <p>>>>>>>> master
   *
   * @throws Exception
   */
  @Test
  public void testReadOnlySpace() throws Exception {
    assertEquals(0, this.spaceRepository.count());
    final Tenant d_tenant = this.createDefaultTenant();
    final Space d_space = this.createSpace(d_tenant, TEST_DEFAULT_SPACE, true);
    assertEquals(1, this.spaceRepository.count());

    // Change to read-only (should work)
    d_space.setReadOnly(true);
    MockHttpServletRequestBuilder post_builder =
        put("/spaces/" + d_space.getSpaceToken())
            .content(JacksonUtil.asJsonString(d_space))
            .contentType(MediaType.APPLICATION_JSON)
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    mockMvc
        .perform(post_builder)
        .andExpect(status().isOk())
        .andExpect(content().contentType(contentType))
        .andExpect(jsonPath("$.default", is(true)))
        .andExpect(jsonPath("$.spaceName", is(TEST_DEFAULT_SPACE)));

    // Change to read-write (should fail)
    d_space.setReadOnly(false);
    post_builder =
        put("/spaces/" + d_space.getSpaceToken())
            .content(JacksonUtil.asJsonString(d_space))
            .contentType(MediaType.APPLICATION_JSON)
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    mockMvc.perform(post_builder).andExpect(status().isBadRequest());
  }

  /**
   * Creates a workspace and searches for it. <<<<<<< HEAD =======
   *
   * <p>>>>>>>> master
   *
   * @throws Exception
   */
  @Test
  public void testSearchSpaces() throws Exception {
    assertEquals(0, this.spaceRepository.count());
    final Tenant d_tenant = this.createDefaultTenant();
    final Space d_space = this.createSpace(d_tenant, TEST_DEFAULT_SPACE, true);
    assertEquals(1, this.spaceRepository.count());

    final String space_name = "foo";
    org.eclipse.steady.shared.json.model.Space new_shared_space =
        new org.eclipse.steady.shared.json.model.Space();
    new_shared_space.setSpaceName("foo");
    new_shared_space.setExportConfiguration(ExportConfiguration.OFF);
    new_shared_space.setSpaceDescription("bar");
    new_shared_space.setOwnerEmails(
        new HashSet<String>(Arrays.asList(new String[] {"foo@bar.com"})));
    final List<org.eclipse.steady.shared.json.model.Property> props =
        new ArrayList<org.eclipse.steady.shared.json.model.Property>();
    props.add(
        new org.eclipse.steady.shared.json.model.Property(
            PropertySource.USER, "propName", "propValue"));
    new_shared_space.setProperties(props);

    // Post w tenant header (should create a NON default space as it's not provided)
    MockHttpServletRequestBuilder post_builder =
        post("/spaces")
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    MvcResult result =
        mockMvc
            .perform(post_builder)
            .andExpect(status().isCreated())
            .andExpect(content().contentType(contentType))
            .andExpect(jsonPath("$.default", is(false)))
            .andExpect(jsonPath("$.spaceName", is(space_name)))
            .andReturn();

    // Create another one (private)
    new_shared_space.setPublic(false);
    post_builder =
        post("/spaces")
            .content(JacksonUtil.asJsonString(new_shared_space))
            .contentType(MediaType.APPLICATION_JSON)
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    result =
        mockMvc
            .perform(post_builder)
            .andExpect(status().isCreated())
            .andExpect(content().contentType(contentType))
            .andExpect(jsonPath("$.default", is(false)))
            .andExpect(jsonPath("$.spaceName", is(space_name)))
            .andReturn();

    assertEquals(3, this.spaceRepository.count());

    // Search : 0 hits
    MockHttpServletRequestBuilder search_builder =
        get("/spaces/search")
            .param("propertyName", "propName")
            .param("value", "xyz", "abc")
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    result = mockMvc.perform(search_builder).andExpect(status().isOk()).andReturn();

    org.eclipse.steady.shared.json.model.Space[] search_result =
        (org.eclipse.steady.shared.json.model.Space[])
            JacksonUtil.asObject(
                result.getResponse().getContentAsString(),
                org.eclipse.steady.shared.json.model.Space[].class);
    assertEquals(0, search_result.length);

    // Search : 1 hit
    search_builder =
        get("/spaces/search")
            .param("propertyName", "propName")
            .param("value", "xyz", "abc", "propValue")
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    result = mockMvc.perform(search_builder).andExpect(status().isOk()).andReturn();

    search_result =
        (org.eclipse.steady.shared.json.model.Space[])
            JacksonUtil.asObject(
                result.getResponse().getContentAsString(),
                org.eclipse.steady.shared.json.model.Space[].class);
    assertEquals(
        1,
        search_result.length); // Should be one public space, the private one shall not be returned

    // Bad reqeust due to lack of search term
    search_builder =
        get("/spaces/search")
            .param("propertyName", "propName")
            .header(Constants.HTTP_TENANT_HEADER, d_tenant.getTenantToken())
            .accept(MediaType.APPLICATION_JSON);
    mockMvc.perform(search_builder).andExpect(status().isBadRequest());
  }

  private Tenant createDefaultTenant() {
    Tenant default_tenant = null;
    try {
      default_tenant =
          TenantRepository.FILTER.findOne(tenantRepository.findBySecondaryKey(TEST_DEFAULT_TENANT));
    } catch (EntityNotFoundException e) {
      default_tenant = new Tenant();
      default_tenant.setTenantToken(TEST_DEFAULT_TENANT);
      default_tenant.setTenantName(TEST_DEFAULT_TENANT);
      default_tenant.setDefault(true);
      tenantRepository.save(default_tenant);

      default_tenant =
          TenantRepository.FILTER.findOne(tenantRepository.findBySecondaryKey(TEST_DEFAULT_TENANT));
    }
    return default_tenant;
  }

  private Space createSpace(Tenant _t, String _token, boolean _default) {
    Space space = null;
    try {
      SpaceRepository.FILTER.findOne(spaceRepository.findBySecondaryKey(_token));

    } catch (EntityNotFoundException e) {
      // default space
      space = new Space();
      space.setSpaceName(_token);
      space.setSpaceToken(_token);
      space.setDefault(_default);
      space.setExportConfiguration(ExportConfiguration.OFF);
      space.setSpaceDescription(_token);
      space.setSpaceOwners(new HashSet<String>(Arrays.asList(new String[] {"foo@bar.com"})));
      space.setTenant(_t);
      spaceRepository.save(space);
    }
    return space;
  }
}
