package app.mealsmadeeasy.api.recipe;

import app.mealsmadeeasy.api.recipe.body.RecipeAiSearchBody;
import app.mealsmadeeasy.api.recipe.body.RecipeSearchBody;
import app.mealsmadeeasy.api.recipe.body.RecipeUpdateBody;
import app.mealsmadeeasy.api.recipe.comment.RecipeComment;
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentCreateBody;
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentService;
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentView;
import app.mealsmadeeasy.api.recipe.converter.RecipeToFullViewConverter;
import app.mealsmadeeasy.api.recipe.converter.RecipeToInfoViewConverter;
import app.mealsmadeeasy.api.recipe.converter.RecipeUpdateBodyToSpecConverter;
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
import app.mealsmadeeasy.api.sliceview.SliceView;
import app.mealsmadeeasy.api.sliceview.SliceViewService;
import app.mealsmadeeasy.api.user.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Nullable;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/recipes")
@RequiredArgsConstructor
public class RecipesController {

    private final RecipeService recipeService;
    private final RecipeStarService recipeStarService;
    private final RecipeCommentService recipeCommentService;
    private final SliceViewService sliceViewService;
    private final ObjectMapper objectMapper;
    private final RecipeToFullViewConverter recipeToFullViewConverter;
    private final RecipeToInfoViewConverter recipeToInfoViewConverter;
    private final RecipeUpdateBodyToSpecConverter updateBodyToSpecConverter;

    private Map<String, Object> getFullViewWrapper(String username, String slug, FullRecipeView view, @Nullable User viewer) {
        Map<String, Object> wrapper = new HashMap<>();
        wrapper.put("recipe", view);
        wrapper.put("isStarred", this.recipeService.isStarer(username, slug, viewer));
        wrapper.put("isOwner", this.recipeService.isOwner(username, slug, viewer));
        return wrapper;
    }

    @GetMapping("/{username}/{slug}")
    public ResponseEntity<Map<String, Object>> getByUsernameAndSlug(
            @PathVariable String username,
            @PathVariable String slug,
            @RequestParam(defaultValue = "false") boolean includeRawText,
            @AuthenticationPrincipal User viewer
    ) {
        final Recipe recipe = this.recipeService.getByUsernameAndSlug(username, slug, viewer);
        final FullRecipeView view = this.recipeToFullViewConverter.convert(recipe, includeRawText, viewer);
        return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, viewer));
    }

    @PutMapping("/{username}/{slug}")
    public ResponseEntity<Map<String, Object>> updateByUsernameAndSlug(
            @PathVariable String username,
            @PathVariable String slug,
            @RequestParam(defaultValue = "true") boolean includeRawText,
            @RequestBody RecipeUpdateBody updateBody,
            @AuthenticationPrincipal User principal
    ) {
        final RecipeUpdateSpec spec = this.updateBodyToSpecConverter.convert(updateBody);
        final Recipe updated = this.recipeService.update(username, slug, spec, principal);
        final FullRecipeView view = this.recipeToFullViewConverter.convert(updated, includeRawText, principal);
        return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, principal));
    }

    @GetMapping
    public ResponseEntity<SliceView<RecipeInfoView>> getRecipeInfoViews(
            Pageable pageable,
            @AuthenticationPrincipal User user
    ) {
        if (user == null) {
            final Slice<Recipe> publicRecipes = this.recipeService.getPublicRecipes(pageable);
            final Slice<RecipeInfoView> publicRecipeInfoViews = publicRecipes.map(
                    recipe -> this.recipeToInfoViewConverter.convert(recipe, null)
            );
            return ResponseEntity.ok(this.sliceViewService.getSliceView(publicRecipeInfoViews));
        } else {
            final Slice<Recipe> recipes = this.recipeService.getViewableBy(pageable, user);
            final Slice<RecipeInfoView> recipeInfoViews = recipes.map(
                    recipe -> this.recipeToInfoViewConverter.convert(recipe, user)
            );
            return ResponseEntity.ok(this.sliceViewService.getSliceView(recipeInfoViews));
        }
    }

    @GetMapping("/meta/count")
    public ResponseEntity<Map<String, Object>> getRecipeCount(@AuthenticationPrincipal User user) {
        final int count;
        if (user == null) {
            count = this.recipeService.countPublicRecipes();
        } else {
            count = this.recipeService.countViewableBy(user);
        }
        return ResponseEntity.ok(Map.of("count", count));
    }

    @PostMapping
    public ResponseEntity<Map<String, Object>> searchRecipes(
            @RequestBody(required = false) RecipeSearchBody recipeSearchBody,
            @AuthenticationPrincipal User user
    ) {
        if (recipeSearchBody.getType() == RecipeSearchBody.Type.AI_PROMPT) {
            final RecipeAiSearchBody spec = this.objectMapper.convertValue(
                    recipeSearchBody.getData(),
                    RecipeAiSearchBody.class
            );
            final List<RecipeInfoView> results = this.recipeService.aiSearch(spec, user)
                    .stream()
                    .map(recipe -> this.recipeToInfoViewConverter.convert(recipe, user))
                    .toList();
            return ResponseEntity.ok(Map.of("results", results));
        } else {
            throw new IllegalArgumentException("Invalid recipeSearchBody type: " + recipeSearchBody.getType());
        }
    }

    @DeleteMapping("/{username}/{slug}")
    public ResponseEntity<Void> deleteRecipe(
            @AuthenticationPrincipal User principal,
            @PathVariable String username,
            @PathVariable String slug
    ) {
        this.recipeService.deleteRecipe(username, slug, principal);
        return ResponseEntity.noContent().build();
    }

    @Deprecated
    @PostMapping("/{username}/{slug}/star")
    public ResponseEntity<RecipeStar> addStar(
            @PathVariable String username,
            @PathVariable String slug,
            @Nullable @AuthenticationPrincipal User principal
    ) {
        return ResponseEntity.status(HttpStatus.CREATED).body(this.recipeStarService.create(username, slug, principal));
    }

    @PostMapping("/{username}/{slug}/star/toggle")
    public ResponseEntity<Object> toggleStar(
            @PathVariable String username,
            @PathVariable String slug,
            @AuthenticationPrincipal User principal
    ) {
        final Optional<RecipeStar> maybeStar = this.recipeStarService.toggle(
                this.recipeService.getByUsernameAndSlug(username, slug, principal),
                principal
        );
        if (maybeStar.isPresent()) {
            return ResponseEntity.status(HttpStatus.CREATED).body(maybeStar.get());
        } else {
            return ResponseEntity.noContent().build();
        }
    }

    @Deprecated
    @GetMapping("/{username}/{slug}/star")
    public ResponseEntity<Map<String, Object>> getStar(
            @PathVariable String username,
            @PathVariable String slug,
            @Nullable @AuthenticationPrincipal User principal
    ) {
        final @Nullable RecipeStar star = this.recipeStarService.find(username, slug, principal).orElse(null);
        if (star != null) {
            return ResponseEntity.ok(Map.of("isStarred", true, "star", star));
        } else {
            return ResponseEntity.ok(Map.of("isStarred", false));
        }
    }

    @Deprecated
    @DeleteMapping("/{username}/{slug}/star")
    public ResponseEntity<Object> removeStar(
            @PathVariable String username,
            @PathVariable String slug,
            @Nullable @AuthenticationPrincipal User principal
    ) {
        this.recipeStarService.delete(username, slug, principal);
        return ResponseEntity.noContent().build();
    }

    @GetMapping("/{username}/{slug}/comments")
    public ResponseEntity<SliceView<RecipeCommentView>> getComments(
            @PathVariable String username,
            @PathVariable String slug,
            Pageable pageable,
            @Nullable @AuthenticationPrincipal User principal
    ) {
        final Slice<RecipeCommentView> slice = this.recipeCommentService.getComments(
                username,
                slug,
                pageable,
                principal
        );
        return ResponseEntity.ok(this.sliceViewService.getSliceView(slice));
    }

    @GetMapping("/{username}/{slug}/comments/count")
    public ResponseEntity<Map<String, Object>> getCommentsCount(
            @PathVariable String username,
            @PathVariable String slug,
            @Nullable @AuthenticationPrincipal User principal
    ) {
        final int count = this.recipeCommentService.countComments(username, slug, principal);
        return ResponseEntity.ok(Map.of("count", count));
    }

    @PostMapping("/{username}/{slug}/comments")
    public ResponseEntity<RecipeCommentView> addComment(
            @PathVariable String username,
            @PathVariable String slug,
            @RequestBody RecipeCommentCreateBody body,
            @Nullable @AuthenticationPrincipal User principal
    ) {
        final RecipeComment comment = this.recipeCommentService.create(username, slug, principal, body);
        return ResponseEntity.ok(RecipeCommentView.from(comment, false));
    }

}
