package org.todo;

import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.todo.model.todo.Todo;
import org.todo.model.todo.Todo.Category;
import org.todo.model.user.User;

import java.time.LocalDate;

import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;

public class TodoRestServiceIT {

	private static final User user = new User("test", "12345");

	private RequestSpecification givenAuth() {
		return given().auth().preemptive().basic(user.getName(), user.getPassword());
	}

	@BeforeAll
	public static void setBaseURI() {
		RestAssured.baseURI = "http://localhost:8080/api";
	}

	@Test
	public void registerUser() {
		User user = new User("alice", "12345");
		given().contentType(JSON).body(user)
				.when().post("/users")
				.then().statusCode(201);
	}

	@Test
	public void registerUserWithoutPassword() {
		User user = new User("alice", null);
		given().contentType(JSON).body(user)
				.when().post("/users")
				.then().statusCode(400);
	}

	@Test
	public void registerUserWithTooShortPassword() {
		User user = new User("alice", "123");
		given().contentType(JSON).body(user)
				.when().post("/users")
				.then().statusCode(400);
	}

	@Test
	public void registerExistingUser() {
		given().contentType(JSON).body(user)
				.when().post("/users")
				.then().statusCode(409);
	}

	@Test
	public void getCategories() {
		givenAuth().accept(JSON)
				.when().get("/categories")
				.then().statusCode(200).body("$", hasSize(5));
	}

	@Test
	public void addTodo() {
		Todo todo = new Todo("Buy milk", Category.HOME, LocalDate.now());
		givenAuth().contentType(JSON).body(todo)
				.when().post("/todos")
				.then().statusCode(201).body("id", notNullValue()).body("completed", equalTo(false));
	}

	@Test
	public void addTodoWithoutTitle() {
		Todo todo = new Todo(null, Category.HOME, LocalDate.now());
		givenAuth().contentType(JSON).body(todo)
				.when().post("/todos")
				.then().statusCode(400);
	}

	@Test
	public void getTodos() {
		Todo todo = new Todo("Buy milk", Category.HOME, LocalDate.now());
		todo = givenAuth().contentType(JSON).body(todo).post("/todos").as(Todo.class);
		givenAuth().accept(JSON)
				.when().get("/todos")
				.then().statusCode(200).body("$", hasItem(hasEntry("id", todo.getId())));
	}

	@Test
	public void getTodosWithCategory() {
		Todo todo = new Todo("Buy milk", Category.HOME, LocalDate.now());
		givenAuth().contentType(JSON).body(todo).post("/todos").as(Todo.class);
		givenAuth().accept(JSON).param("category", Category.HOME)
				.when().get("/todos")
				.then().statusCode(200).body("$", not(empty()));
		givenAuth().accept(JSON).param("category", Category.PERSONAL)
				.when().get("/todos")
				.then().statusCode(200).body("$", empty());
	}

	@Test
	public void getTodosWithoutAuthentication() {
		given().accept(JSON)
				.when().get("/todos")
				.then().statusCode(401);
	}

	@Test
	public void getTodosWithInvalidPassword() {
		given().auth().preemptive().basic(user.getName(), "123").accept(JSON)
				.when().get("/todos")
				.then().statusCode(401);
	}

	@Test
	public void getTodo() {
		Todo todo = new Todo("Buy milk", Category.HOME, LocalDate.now());
		todo = givenAuth().contentType(JSON).body(todo).post("/todos").as(Todo.class);
		givenAuth().accept(JSON)
				.when().get("/todos/" + todo.getId())
				.then().statusCode(200).body("title", equalTo("Buy milk"));
	}

	@Test
	public void getNonExistingTodo() {
		givenAuth().accept(JSON)
				.when().get("/todos/0")
				.then().statusCode(404);
	}

	@Test
	public void updateTodo() {
		Todo todo = new Todo("Buy milk", Category.HOME, LocalDate.now());
		todo = givenAuth().contentType(JSON).body(todo).post("/todos").as(Todo.class);
		todo.setCompleted(true);
		givenAuth().contentType(JSON).body(todo)
				.when().put("/todos/" + todo.getId())
				.then().statusCode(200);
		givenAuth().accept(JSON)
				.when().get("/todos/" + todo.getId())
				.then().statusCode(200).body("completed", equalTo(true));
	}

	@Test
	public void updateTodoWithWrongId() {
		Todo todo = new Todo("Buy milk", Category.HOME, LocalDate.now());
		todo = givenAuth().contentType(JSON).body(todo).post("/todos").as(Todo.class);
		int id = todo.getId();
		todo.setId(0);
		givenAuth().contentType(JSON).body(todo)
				.when().put("/todos/" + id)
				.then().statusCode(400);
	}

	@Test
	public void deleteTodo() {
		Todo todo = new Todo("Buy milk", Category.HOME, LocalDate.now());
		todo = givenAuth().contentType(JSON).body(todo).post("/todos").as(Todo.class);
		givenAuth()
				.when().delete("/todos/" + todo.getId())
				.then().statusCode(204);
		givenAuth().accept(JSON)
				.when().get("/todos/" + todo.getId())
				.then().statusCode(404);
	}

	@Test
	public void deleteNonExistingTodo() {
		givenAuth()
				.when().delete("/todos/0")
				.then().statusCode(404);
	}
}
