package app.mealsmadeeasy.api.recipe;

import app.mealsmadeeasy.api.PostgresTestsExtension;
import app.mealsmadeeasy.api.image.Image;
import app.mealsmadeeasy.api.image.ImageRepository;
import app.mealsmadeeasy.api.recipe.comment.RecipeComment;
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentRepository;
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
import app.mealsmadeeasy.api.recipe.star.RecipeStarRepository;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Pageable;

import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ExtendWith(PostgresTestsExtension.class)
public class RecipeRepositoryTests {

    @Autowired
    private RecipeRepository recipeRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RecipeStarRepository recipeStarRepository;

    @Autowired
    private RecipeCommentRepository recipeCommentRepository;

    @Autowired
    private ImageRepository imageRepository;

    private User seedUser() {
        final String uuid = UUID.randomUUID().toString();
        final User draft = User.getDefaultDraft();
        draft.setUsername(uuid);
        draft.setEmail(uuid + "@test.com");
        draft.setPassword("test");
        return this.userRepository.save(draft);
    }

    private Recipe seedRecipe(User owner) {
        final Recipe recipe = new Recipe();
        recipe.setOwner(owner);
        recipe.setTitle("Test Recipe");
        recipe.setSlug(UUID.randomUUID().toString());
        recipe.setRawText("Hello, World!");
        return this.recipeRepository.save(recipe);
    }

    @Test
    public void findsAllPublicRecipes() {
        final Recipe publicRecipe = new Recipe();
        publicRecipe.setCreated(OffsetDateTime.now());
        publicRecipe.setSlug(UUID.randomUUID().toString());
        publicRecipe.setIsPublic(true);
        publicRecipe.setOwner(this.seedUser());
        publicRecipe.setTitle("Public Recipe");
        publicRecipe.setRawText("Hello, World!");
        final Recipe savedRecipe = this.recipeRepository.save(publicRecipe);

        final List<Recipe> publicRecipes = this.recipeRepository.findAllByIsPublicIsTrue(Pageable.unpaged()).toList();
        assertThat(publicRecipes).anyMatch(recipeEntity -> recipeEntity.getId().equals(savedRecipe.getId()));
    }

    @Test
    public void doesNotFindNonPublicRecipe() {
        final Recipe nonPublicRecipe = new Recipe();
        nonPublicRecipe.setCreated(OffsetDateTime.now());
        nonPublicRecipe.setSlug(UUID.randomUUID().toString());
        nonPublicRecipe.setOwner(this.seedUser());
        nonPublicRecipe.setTitle("Non-Public Recipe");
        nonPublicRecipe.setRawText("Hello, World!");
        final Recipe savedRecipe = this.recipeRepository.save(nonPublicRecipe);

        final List<Recipe> publicRecipes = this.recipeRepository.findAllByIsPublicIsTrue(Pageable.unpaged()).toList();
        assertThat(publicRecipes).noneMatch(recipeEntity -> recipeEntity.getId().equals(savedRecipe.getId()));
    }

    @Test
    public void findsAllForViewer() {
        final Recipe recipe = new Recipe();
        recipe.setCreated(OffsetDateTime.now());
        recipe.setSlug(UUID.randomUUID().toString());
        recipe.setOwner(this.seedUser());
        recipe.setTitle("Test Recipe");
        recipe.setRawText("Hello, World!");
        final Recipe saved = this.recipeRepository.save(recipe);

        final User viewer = this.seedUser();
        final Set<User> viewers = new HashSet<>(recipe.getViewers());
        viewers.add(viewer);
        saved.setViewers(viewers);

        this.recipeRepository.save(saved);

        final List<Recipe> viewable = this.recipeRepository.findAllByViewersContaining(viewer);
        assertThat(viewable.size()).isEqualTo(1);
    }

    @Test
    public void doesNotIncludeNonViewable() {
        final Recipe recipe = new Recipe();
        recipe.setCreated(OffsetDateTime.now());
        recipe.setSlug(UUID.randomUUID().toString());
        recipe.setOwner(this.seedUser());
        recipe.setTitle("Test Recipe");
        recipe.setRawText("Hello, World!");
        this.recipeRepository.save(recipe);

        final User viewer = this.seedUser();
        final List<Recipe> viewable = this.recipeRepository.findAllByViewersContaining(viewer);
        assertThat(viewable.size()).isEqualTo(0);
    }

    @Test
    public void deleteRecipeWithStar() {
        final User user = this.seedUser();
        final Recipe recipe = this.seedRecipe(user);
        final RecipeStar star = new RecipeStar();
        star.setOwner(user);
        star.setRecipe(recipe);
        recipe.getStars().add(star);
        this.recipeStarRepository.save(star);
        assertDoesNotThrow(() -> this.recipeRepository.delete(recipe));
    }

    @Test
    public void deleteRecipeWithComment() {
        final User user = this.seedUser();
        final Recipe recipe = this.seedRecipe(user);
        final RecipeComment recipeComment = new RecipeComment();
        recipeComment.setRecipe(recipe);
        recipeComment.setOwner(user);
        recipeComment.setRawText("Hello, World!");
        recipe.getComments().add(recipeComment);
        this.recipeCommentRepository.save(recipeComment);
        assertDoesNotThrow(() -> this.recipeRepository.delete(recipe));
    }

    @Test
    public void deleteRecipeWithViewer() {
        final User user = this.seedUser();
        final Recipe recipe = this.seedRecipe(user);
        recipe.getViewers().add(user);
        this.recipeRepository.save(recipe);
        assertDoesNotThrow(() -> this.recipeRepository.delete(recipe));
    }

    @Test
    public void deleteRecipeWithMainImage() {
        final User user = this.seedUser();
        final Recipe recipe = this.seedRecipe(user);

        final Image mainImage = new Image();
        mainImage.setUserFilename(UUID.randomUUID().toString());
        mainImage.setMimeType("image/jpeg");
        mainImage.setObjectName(UUID.randomUUID().toString());
        mainImage.setOwner(user);
        final Image savedMainImage = this.imageRepository.save(mainImage);

        recipe.setSlug(UUID.randomUUID().toString());
        recipe.setTitle("Hello, World!");
        recipe.setRawText("# Hello, World!");
        recipe.setMainImage(savedMainImage);
        this.recipeRepository.save(recipe);

        assertDoesNotThrow(() -> this.recipeRepository.delete(recipe));
    }

}
