package org.bookstore.order.controller;

import org.bookstore.order.dto.PurchaseOrder;
import org.bookstore.order.dto.PurchaseOrderItem;
import org.bookstore.order.dto.SalesOrder;
import org.bookstore.order.entity.Book;
import org.bookstore.order.entity.OrderStatus;
import org.bookstore.order.integration.CatalogClient;
import org.bookstore.order.integration.ShippingClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.web.client.RestClient;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import static org.springframework.web.client.HttpClientErrorException.NotFound;
import static org.springframework.web.client.HttpClientErrorException.UnprocessableEntity;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class OrderControllerIT {

	private final RestClient restClient;
	@MockitoBean
	private CatalogClient catalogClient;
	@MockitoBean
	private ShippingClient shippingClient;

	public OrderControllerIT(@LocalServerPort int port) {
		restClient = RestClient.create("http://localhost:" + port + "/orders");
	}

	@BeforeEach
	public void setupMocks() throws Exception {
		Book book = new Book("1000000001", "Title", "Authors", "Publisher", new BigDecimal("10.00"));
		Mockito.when(catalogClient.findBook(any())).thenReturn(book);
	}

	@Test
	public void placeOrder() {
		PurchaseOrder purchaseOrder = new PurchaseOrder(1, List.of(new PurchaseOrderItem("1000000001", 2)));
		ResponseEntity<SalesOrder> response =
				restClient.post().body(purchaseOrder).retrieve().toEntity(SalesOrder.class);
		assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
		SalesOrder salesOrder = response.getBody();
		assertThat(salesOrder.date().toLocalDate()).isEqualTo(LocalDate.now());
		assertThat(salesOrder.status()).isEqualTo(OrderStatus.ACCEPTED);
		assertThat(salesOrder.amount()).isEqualTo(new BigDecimal("20.00"));
	}

	@Test
	public void placeOrderForNonExistingCustomer() {
		PurchaseOrder purchaseOrder = new PurchaseOrder(0, List.of(new PurchaseOrderItem("1000000001", 2)));
		assertThatExceptionOfType(UnprocessableEntity.class)
				.isThrownBy(() -> restClient.post().body(purchaseOrder).retrieve().toBodilessEntity());
	}

	@Test
	public void placeOrderWithExpiredCreditCard() {
		PurchaseOrder purchaseOrder = new PurchaseOrder(2, List.of(new PurchaseOrderItem("1000000001", 2)));
		assertThatExceptionOfType(UnprocessableEntity.class)
				.isThrownBy(() -> restClient.post().body(purchaseOrder).retrieve().toBodilessEntity());
	}

	@Test
	public void findOrder() {
		ResponseEntity<SalesOrder> response = restClient.get().uri("/{id}", 1).retrieve().toEntity(SalesOrder.class);
		assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
		assertThat(response.getBody().id()).isEqualTo(1);
	}

	@Test
	public void findNonExistingOrder() {
		assertThatExceptionOfType(NotFound.class)
				.isThrownBy(() -> restClient.get().uri("/{id}", 0).retrieve().toBodilessEntity());
	}

	@Test
	public void searchOrders() {
		ResponseEntity<SalesOrder[]> response =
				restClient.get().uri("?customerId={0}&year={1}", 1, 2020).retrieve().toEntity(SalesOrder[].class);
		assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
		assertThat(response.getBody()).hasSize(2);
	}

	@Test
	public void searchNonExistingOrders() {
		ResponseEntity<SalesOrder[]> response =
				restClient.get().uri("?customerId={0}&year={1}", 1, 2021).retrieve().toEntity(SalesOrder[].class);
		assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
		assertThat(response.getBody()).isEmpty();
	}

	@Test
	public void searchOrdersOfNonExistingCustomer() {
		assertThatExceptionOfType(NotFound.class)
				.isThrownBy(() -> restClient.get().uri("?customerId={0}&year={1}", 0, 2020).retrieve().toBodilessEntity());
	}
}
