package com.example.demo.service.Impl;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.example.demo.dto.RecipeDto;
import com.example.demo.dto.UserDto;
import com.example.demo.dto.StepDto;
import com.example.demo.dto.TagDto;
import com.example.demo.dto.ImageDto;
import com.example.demo.dto.RecipeIngredientDto;
import com.example.demo.entity.Image;
import com.example.demo.entity.Ingredient;
import com.example.demo.entity.Recipe;
import com.example.demo.entity.RecipeIngredient;
import com.example.demo.entity.Step;
import com.example.demo.entity.Tag;
import com.example.demo.entity.User;
import com.example.demo.exception.NotFoundException;
import com.example.demo.repository.ImageRepo;
import com.example.demo.repository.IngredientRepo;
import com.example.demo.repository.RecipeIngredientRepo;
import com.example.demo.repository.RecipeRepo;
import com.example.demo.repository.StepRepo;
import com.example.demo.repository.TagRepo;
import com.example.demo.repository.UserRepo;
import com.example.demo.service.RecipeService;

import jakarta.transaction.Transactional;

@Service
public class RecipeServiceImpl implements RecipeService {

	private RecipeRepo recipeRepository;
	private IngredientRepo ingredientRepository;
	private RecipeIngredientRepo recipeIngredientRepository;
	private UserRepo userRepository;
	private StepRepo stepRepository;
	private ImageRepo imageRepository;
	private TagRepo tagRepository;

	public RecipeServiceImpl(RecipeRepo recipeRepository, IngredientRepo ingredientRepository,
			RecipeIngredientRepo recipeIngredientRepository, UserRepo userRepository, StepRepo stepRepository,
			ImageRepo imageRepository, TagRepo tagRepository) {
		super();
		this.recipeRepository = recipeRepository;
		this.ingredientRepository = ingredientRepository;
		this.recipeIngredientRepository = recipeIngredientRepository;
		this.userRepository = userRepository;
		this.stepRepository = stepRepository;
		this.imageRepository = imageRepository;
		this.tagRepository = tagRepository;
	}

	public RecipeDto convertToDto(Recipe recipe) {
		List<RecipeIngredientDto> ingredientDtos = recipe.getRecipeIngredients().stream()
				.map(ri -> new RecipeIngredientDto(ri.getIngredient().getName(), ri.getQuantity(), ri.getUnit(),
						ri.getNotes()))
				.toList();

		List<StepDto> stepDtos = recipe.getSteps().stream()
				.map(ri -> new StepDto(ri.getStepNumber(), ri.getInstruction())).toList();

		List<ImageDto> imageDtos = recipe.getImages().stream().map(ri -> new ImageDto(ri.getImageUrl())).toList();

		List<TagDto> tagDtos = recipe.getTags().stream().map(ri -> new TagDto(ri.getName())).toList();

		UserDto userDto = new UserDto(recipe.getUser().getId(), recipe.getUser().getUsername(),
				recipe.getUser().getEmail());

		return new RecipeDto(recipe.getTitle(), recipe.getDescription(), recipe.getPrepTimeMinutes(),
				recipe.getCookTimeMinutes(), recipe.getServings(), userDto, recipe.getStatus(), ingredientDtos,
				stepDtos, imageDtos, tagDtos);
	}

	@Override
	public RecipeDto saveRecipe(RecipeDto dto) {

		User user = userRepository.findById(dto.getUserDto().getId())
				.orElseThrow(() -> new NotFoundException("User", "id", dto.getUserDto().getId()));

		Recipe recipe = new Recipe(dto.getTitle(), dto.getDescription(), dto.getPrepTimeMinutes(),
				dto.getCookTimeMinutes(), dto.getServings(), user, dto.getStatus());

		for (RecipeIngredientDto riDto : dto.getIngredients()) {

			Ingredient ingredient = ingredientRepository.findByNameIgnoreCase(riDto.getIngredientName())
					.orElseGet(() -> new Ingredient(riDto.getIngredientName()));

			if (ingredient.getId() == null) {
				ingredientRepository.save(ingredient);
			}

			RecipeIngredient ri = new RecipeIngredient(recipe, ingredient, riDto.getQuantity(), riDto.getUnit(),
					riDto.getNotes());

			recipe.getRecipeIngredients().add(ri);
		}

		if (dto.getSteps() != null) {
			for (StepDto stepDto : dto.getSteps()) {
				Step step = new Step(recipe, stepDto.getStepNumber(), stepDto.getInstruction());
				recipe.getSteps().add(step);
			}
		}

		if (dto.getImages() != null) {
			for (ImageDto imageDto : dto.getImages()) {
				Image image = new Image(recipe, imageDto.getImageUrl());
				recipe.getImages().add(image);
			}
		}

		for (TagDto tDto : dto.getTags()) {

			Tag tag = tagRepository.findByName(tDto.getName()).orElseGet(() -> new Tag(tDto.getName()));

			if (tag.getId() == null) {
				tagRepository.save(tag);
			}
			recipe.getTags().add(tag);
		}

		Recipe saved = recipeRepository.save(recipe);

		return getRecipeById(saved.getId());
		//return convertToDto(saved);
	}

	@Override
	@Transactional
	public List<RecipeDto> getAllRecipes() {

		List<RecipeDto> list = new ArrayList<>();
		for (Recipe recipe : recipeRepository.findAll()) {
			RecipeDto recipeDto = convertToDto(recipe);
			list.add(recipeDto);
		}

		return list;
	}

	@Override
	@Transactional
	public RecipeDto getRecipeById(Integer Id) {
		return convertToDto(recipeRepository.findById(Id).orElseThrow(() -> new NotFoundException("Recipe", "id", Id)));
	}

	@Override
	@Transactional
	public RecipeDto updateRecipe(RecipeDto recipeDto, Integer id) {
		Recipe existingRecipe = recipeRepository.findById(id)
				.orElseThrow(() -> new NotFoundException("Recipe", "id", id));

		existingRecipe.setTitle(recipeDto.getTitle());
		existingRecipe.setDescription(recipeDto.getDescription());
		existingRecipe.setPrepTimeMinutes(recipeDto.getPrepTimeMinutes());
		existingRecipe.setCookTimeMinutes(recipeDto.getCookTimeMinutes());
		existingRecipe.setServings(recipeDto.getServings());
		existingRecipe.setStatus(recipeDto.getStatus());

		List<RecipeIngredientDto> updatedIngredients = recipeDto.getIngredients();
		List<RecipeIngredient> ingredientsToRemove = new ArrayList<>();

		List<StepDto> updatedSteps = recipeDto.getSteps();
		List<Step> stepsToRemove = new ArrayList<>();

		List<ImageDto> updatedImages = recipeDto.getImages();
		List<Image> imagesToRemove = new ArrayList<>();

		List<TagDto> updatedTags = recipeDto.getTags();
		List<Tag> tagsToRemove = new ArrayList<>();

		for (RecipeIngredient ri : existingRecipe.getRecipeIngredients()) {

			boolean existsInUpdatedList = false;
			for (RecipeIngredientDto dto : updatedIngredients) {
				String updatedName = dto.getIngredientName();
				String existingName = ri.getIngredient().getName();

				if (updatedName.equals(existingName)) {
					existsInUpdatedList = true;
					break;
				}
			}

			if (!existsInUpdatedList) {
				ingredientsToRemove.add(ri);
			}
		}

		existingRecipe.getRecipeIngredients().removeAll(ingredientsToRemove);

		for (RecipeIngredientDto riDto : updatedIngredients) {

			// go through the old list of ingredients until we find a match with updated
			// list
			RecipeIngredient existingRI = existingRecipe.getRecipeIngredients().stream()
					.filter(ri -> ri.getIngredient().getName().equals(riDto.getIngredientName())).findFirst()
					.orElse(null);

			// if old ingredient just update parameters
			if (existingRI != null) {

				existingRI.setQuantity(riDto.getQuantity());
				existingRI.setUnit(riDto.getUnit());
				existingRI.setNotes(riDto.getNotes());
			}

			// if new ingredient, have to make a whole new thing
			else {

				Ingredient ingredient = ingredientRepository.findByNameIgnoreCase(riDto.getIngredientName())
						.orElseGet(() -> new Ingredient(riDto.getIngredientName()));

				if (ingredient.getId() == null) {
					ingredientRepository.save(ingredient);
				}

				RecipeIngredient newRI = new RecipeIngredient(existingRecipe, ingredient, riDto.getQuantity(),
						riDto.getUnit(), riDto.getNotes());

				existingRecipe.getRecipeIngredients().add(newRI);
			}
		}

		if (updatedSteps != null) {
			// find steps that weren't included
			for (Step step : existingRecipe.getSteps()) {
				boolean existsInUpdatedList = updatedSteps.stream()
						.anyMatch(dto -> dto.getStepNumber().equals(step.getStepNumber()));

				if (!existsInUpdatedList)
					stepsToRemove.add(step);
			}
			// delete those steps
			existingRecipe.getSteps().removeAll(stepsToRemove);

			// go through updated steps
			for (StepDto stepDto : updatedSteps) {

				// find matching step by step number
				Step existingStep = existingRecipe.getSteps().stream()
						.filter(s -> s.getStepNumber().equals(stepDto.getStepNumber())).findFirst().orElse(null);

				// if there's a match update the instruction string
				if (existingStep != null) {
					existingStep.setInstruction(stepDto.getInstruction());
				}

				// if no match then make a whole new step
				else {
					Step newStep = new Step(existingRecipe, stepDto.getStepNumber(), stepDto.getInstruction());
					existingRecipe.getSteps().add(newStep);
				}
			}
		}

		// same process as above just with images instead
		if (updatedImages != null) {
			for (Image image : existingRecipe.getImages()) {
				boolean existsInUpdatedList = updatedImages.stream()
						.anyMatch(dto -> dto.getImageUrl().equals(image.getImageUrl()));
				if (!existsInUpdatedList)
					imagesToRemove.add(image);
			}

			existingRecipe.getImages().removeAll(imagesToRemove);

			for (ImageDto imageDto : updatedImages) {
				Image existingImage = existingRecipe.getImages().stream()
						.filter(img -> img.getImageUrl().equals(imageDto.getImageUrl())).findFirst().orElse(null);

				if (existingImage != null) {
					existingImage.setImageUrl(imageDto.getImageUrl());
				} else {
					Image newImage = new Image(existingRecipe, imageDto.getImageUrl());
					existingRecipe.getImages().add(newImage);
				}
			}
		}

		// same process as above just with tags instead, except for saving the tag
		// since the relationship for this one was slightly different
		if (updatedTags != null) {
			for (Tag tag : existingRecipe.getTags()) {
				boolean existsInUpdatedList = updatedTags.stream().anyMatch(dto -> dto.getName().equals(tag.getName()));
				if (!existsInUpdatedList)
					tagsToRemove.add(tag);
			}

			existingRecipe.getTags().removeAll(tagsToRemove);

			for (TagDto tagDto : updatedTags) {
				Tag existingTag = existingRecipe.getTags().stream()
						.filter(tag -> tag.getName().equals(tagDto.getName())).findFirst().orElse(null);

				if (existingTag != null) {
					existingTag.setName(tagDto.getName());
				} else {
					Tag newTag = tagRepository.findByName(tagDto.getName())
							.orElseGet(() -> tagRepository.save(new Tag(tagDto.getName())));

					existingRecipe.getTags().add(newTag);
				}
			}
		}
		recipeRepository.save(existingRecipe);
		return convertToDto(existingRecipe);
	}

	@Override
	public void deleteRecipe(Integer Id) {
		recipeRepository.findById(Id).orElseThrow(() -> new NotFoundException("Recipe", "id", Id));
		recipeRepository.deleteById(Id);

	}
}