package com.packtpub.mmj.booking.resources;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.packtpub.mmj.booking.BookingApp;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
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.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.stream.annotation.StreamMessageConverter;
import org.springframework.cloud.stream.schema.avro.AvroSchemaMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.MimeType;

/**
 * Spring System test - by using @SpringApplicationConfiguration that picks up same configuration
 * that Spring Boot uses.
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BookingApp.class, webEnvironment = WebEnvironment.RANDOM_PORT,
    properties = {"command.autorun.enabled=false"})
public class BookingControllerIntegrationTests {

  //Required to Generate JSON content from Java objects
  @Autowired
  private ObjectMapper objectMapper;

  private final TestRestTemplate restTemplate = new TestRestTemplate();
  @LocalServerPort
  private int port;

  /**
   * Test the GET /v1/booking/{id} API
   */
  @Test
  public void testGetById() {
    //API call
    Map<String, Object> response
        = restTemplate.getForObject("http://localhost:" + port + "/v1/booking/1", Map.class);

    assertNotNull(response);

    //Asserting API Response
    String id = response.get("id").toString();
    assertNotNull(id);
    assertEquals("1", id);
    String name = response.get("name").toString();
    assertNotNull(name);
    assertEquals("Booking 1", name);
    boolean isModified = (boolean) response.get("isModified");
    assertEquals(false, isModified);
  }

  /**
   * Test the GET /v1/booking/{id} API for no content
   */
  @Test
  public void testGetById_NoContent() {

    HttpHeaders headers = new HttpHeaders();
    HttpEntity<Object> entity = new HttpEntity<>(headers);
    ResponseEntity<Map> responseE = restTemplate
        .exchange("http://localhost:" + port + "/v1/booking/99", HttpMethod.GET, entity, Map.class);

    assertNotNull(responseE);

    // Should return no content as there is no booking with id 99
    assertEquals(HttpStatus.NO_CONTENT, responseE.getStatusCode());
  }

  /**
   * Test the GET /v1/booking API
   */
  @Test
  public void testGetByName() {

    HttpHeaders headers = new HttpHeaders();
    HttpEntity<Object> entity = new HttpEntity<>(headers);
    Map<String, Object> uriVariables = new HashMap<>();
    uriVariables.put("name", "Booking");
    ResponseEntity<Map[]> responseE = restTemplate
        .exchange("http://localhost:" + port + "/v1/booking/?name={name}", HttpMethod.GET, entity,
            Map[].class, uriVariables);

    assertNotNull(responseE);

    // Should return no content as there is no booking with id 99
    assertEquals(HttpStatus.OK, responseE.getStatusCode());
    Map<String, Object>[] responses = responseE.getBody();
    assertNotNull(responses);

    // Assumed only single instance exist for booking name contains word "Big"
    assertTrue(responses.length == 2);

    Map<String, Object> response = responses[0];
    String id = response.get("id").toString();
    assertNotNull(id);
    assertEquals("1", id);
    String name = response.get("name").toString();
    assertNotNull(name);
    assertEquals("Booking 1", name);
    boolean isModified = (boolean) response.get("isModified");
    assertEquals(false, isModified);
  }

  /**
   * Test the POST /v1/booking API
   */
  @Test
  public void testAdd() throws JsonProcessingException {

    Map<String, Object> requestBody = new HashMap<>();
    requestBody.put("name", "TestBkng 3");
    requestBody.put("id", "3");
    requestBody.put("userId", "3");
    requestBody.put("restaurantId", "1");
    requestBody.put("tableId", "1");
    LocalDate nowDate = LocalDate.now();
    LocalTime nowTime = LocalTime.now();
    requestBody.put("date", nowDate);
    requestBody.put("time", nowTime);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    objectMapper.findAndRegisterModules();
    HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody),
        headers);

    ResponseEntity<Map> responseE = restTemplate
        .exchange("http://localhost:" + port + "/v1/booking", HttpMethod.POST, entity, Map.class,
            Collections.EMPTY_MAP);

    assertNotNull(responseE);

    // Should return created (status code 201)
    assertEquals(HttpStatus.CREATED, responseE.getStatusCode());

    //validating the newly created booking using API call
    Map<String, Object> response
        = restTemplate.getForObject("http://localhost:" + port + "/v1/booking/3", Map.class);

    assertNotNull(response);

    //Asserting API Response
    String id = response.get("id").toString();
    assertNotNull(id);
    assertEquals("3", id);
    String name = response.get("name").toString();
    assertNotNull(name);
    assertEquals("TestBkng 3", name);
    boolean isModified = (boolean) response.get("isModified");
    assertEquals(false, isModified);
    String userId = response.get("userId").toString();
    assertNotNull(userId);
    assertEquals("3", userId);
    String restaurantId = response.get("restaurantId").toString();
    assertNotNull(restaurantId);
    assertEquals("1", restaurantId);
    String tableId = response.get("tableId").toString();
    assertNotNull(tableId);
    assertEquals("1", tableId);
    String date1 = response.get("date").toString();
    assertNotNull(date1);
    DateTimeFormatter DateFormatter = new DateTimeFormatterBuilder().parseCaseInsensitive()
        .appendPattern("uuuu-MM-dd").toFormatter(Locale.ENGLISH);
    assertEquals(nowDate, LocalDate.parse(date1, DateFormatter));

    String time1 = response.get("time").toString();
    assertNotNull(time1);
    /*
    DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder().parseCaseInsensitive()
        .appendPattern("[[HH][:mm][:ss][.SSSSSSS]]")
        .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
        .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
        .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).toFormatter(Locale.ENGLISH);*/
    assertEquals(nowTime, LocalTime.parse(time1, DateTimeFormatter.ISO_LOCAL_TIME));
  }

}