package ru.kayashov.bar.mapper;

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.RatingResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
import ru.kayashov.bar.model.entity.BarEntity;
import ru.kayashov.bar.model.entity.BarIngredientStorage;
import ru.kayashov.bar.model.entity.BarResident;
import ru.kayashov.bar.model.entity.CocktailEntity;
import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.model.entity.Rating;
import ru.kayashov.bar.model.entity.ReceiptEntity;
import ru.kayashov.bar.model.entity.StopList;
import ru.kayashov.bar.model.entity.TagEntity;
import ru.kayashov.bar.model.entity.Visitor;
import ru.kayashov.bar.repository.VisitorsRepository;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class CocktailMapper {

    private final VisitorsRepository visitorsRepository;

    @Transactional
    public List<CocktailForListResponseDto> cocktailsToDtoList(List<CocktailEntity> cocktails, Boolean all) {
        Visitor visitor = getCurrentVisitor();
//        if(checkUserNotInBar(visitor)) {
//            return new ArrayList<>();
//        }
        List<Long> barStopList = getStopList(visitor);
        List<Long> allowedIngredients = getAllowedIngredients(visitor);
        return cocktails.stream()
                .map(c -> cocktailToDto(c, visitor, allowedIngredients, barStopList))
//                .filter(c -> all || c.getIsAllowed())
//                .filter(c -> all || c.getInMenu())
                .toList();
    }

    public CocktailForListResponseDto cocktailToFullDto(CocktailEntity e) {
        List<Long> allowed = getAllowedIngredients(getCurrentVisitor());
        return CocktailForListResponseDto.builder()
                .id(e.getId())
                .name(e.getName())
                .image(e.getImage())
                .category(e.getCategoryEntity().getName())
                .alcoholic(e.getAlcoholicEntity().getName())
                .glass(e.getGlassEntity().getName())
                .tags(containCocktailTags(e.getTags()))
                .instructions(e.getInstructions())
                .video(e.getVideo())
                .receipt(createReceiptDtoList(e.getReceipt(), allowed))
                .build();
    }

    private boolean checkUserNotInBar(Visitor visitor) {
        return visitor.getResidents().stream()
                .filter(BarResident::getActive)
                .map(BarResident::getBar)
                .toList()
                .isEmpty();
    }

    private CocktailForListResponseDto cocktailToDto(CocktailEntity e, Visitor visitor, List<Long> allowedIngredients, List<Long> barStopList) {
        boolean hasError = false;
        int volume = 0;
        Float abv = 0f;
        Integer p = null;
        for(ReceiptEntity receipt : e.getReceipt()) {
            if(receipt.getCount() == null) {
                hasError = true;
                break;
            }
            if(receipt.getUnit() == null) {
                hasError = true;
                break;
            }
            if(!receipt.getUnit().getId().equals(74L) && abv != null) {
                abv = null;
            }
            if(abv != null) {
                IngredientEntity ingredient = receipt.getIngredient();
                if(ingredient.getAlcohol() && ingredient.getAbv() == null) {
                    hasError = true;
                    break;
                }
                if(ingredient.getAlcohol()) {
                    abv += ingredient.getAbv() * receipt.getCount();
                }
                volume += receipt.getCount();
            }
        }

        if(!hasError && abv != null) {
            p = Math.round(abv / volume);
        }

        return CocktailForListResponseDto.builder()
                .id(e.getId())
                .name(e.getName())
                .image(e.getImage())
                .hasError(hasError)
                .volume(p)
                .category(e.getCategoryEntity().getName())
                .alcoholic(e.getAlcoholicEntity().getName())
                .glass(e.getGlassEntity().getName())
                .tags(containCocktailTags(e.getTags()))
                .components(containCocktailComponents(e.getReceipt()))
                .rating(createRatingDto(e.getRating(), visitor))
                .isAllowed(calculateAllowed(e.getReceipt(), allowedIngredients))
                .inMenu(!barStopList.contains(e.getId()))
                .build();
//        d.setReceipt(e.getReceipt().stream().map(ReceiptResponseDto::mapToDto).toList());
    }

    private List<Long> getStopList(Visitor visitor) {
        return visitor.getResidents().stream()
                .filter(BarResident::getActive)
                .map(BarResident::getBar)
                .map(BarEntity::getStops)
                .flatMap(List::stream)
                .map(StopList::getCocktail)
                .map(CocktailEntity::getId)
                .toList();
    }

    private List<Long> getAllowedIngredients(Visitor visitor) {
        return visitor.getResidents().stream()
                .filter(BarResident::getActive)
                .map(BarResident::getBar)
                .map(BarEntity::getIngredients)
                .flatMap(List::stream)
                .map(BarIngredientStorage::getIngredient)
                .map(IngredientEntity::getId)
                .toList();
    }

    private Boolean calculateAllowed(List<ReceiptEntity> e, List<Long> allowedIngredients) {
        return e.stream()
                .map(ReceiptEntity::getIngredient)
                .allMatch(i -> allowedIngredients.contains(i.getId()));
    }

    private RatingResponseDto createRatingDto(List<Rating> rating, Visitor visitor) {
        RatingResponseDto result = new RatingResponseDto();
        int sum = 0;
        int count = 0;
        for (Rating current : rating) {
            if (current.getVisitor().getId().equals(visitor.getId())) {
                result.setFavourite(current.isFavorite());
                if (current.getRating() != 0) {
                    result.setRating(current.getRating());
                    return result;
                }
            } else if (current.getRating() > 0) {
                sum += current.getRating();
                count++;
            }
        }
        if (sum > 0) {
            result.setRating(sum / count);
        }
        return result;
    }

    private String containCocktailComponents(List<ReceiptEntity> receipts) {
        return receipts.stream()
                .map(ReceiptEntity::getIngredient)
                .map(IngredientEntity::getName)
                .collect(Collectors.joining(", "));
    }

    private String containCocktailTags(List<TagEntity> tags) {
        return tags.stream()
                .map(TagEntity::getName)
                .collect(Collectors.joining(","));
    }

    private Visitor getCurrentVisitor() {
        Long visitorId = ((Visitor) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
        return visitorsRepository.findById(visitorId).orElseThrow(RuntimeException::new);
    }

    public CocktailModalDto cocktailToModalDto(CocktailEntity e) {
        Visitor visitor = getCurrentVisitor();
        List<Long> allowedIngredients = getAllowedIngredients(visitor);
        return CocktailModalDto.builder()
                .id(e.getId())
                .name(e.getName())
                .image(e.getImage())
                .instructions(e.getInstructions())
                .rating(createRatingDto(e.getRating(), visitor))
                .receipt(createReceiptDtoList(e.getReceipt(), allowedIngredients))
                .build();
    }

    private List<ReceiptResponseDto> createReceiptDtoList(List<ReceiptEntity> receipts, List<Long> allowedIngredients) {
        return receipts.stream()
                .map(r -> createReceiptDto(r, allowedIngredients))
                .toList();
    }

    private ReceiptResponseDto createReceiptDto(ReceiptEntity e, List<Long> allowedIngredients) {
        return ReceiptResponseDto.builder()
                .id(e.getId())
                .ingredient(createIngredientResponseDto(e.getIngredient(), allowedIngredients))
                .count(e.getCount())
                .unit(e.getUnit())
                .measure(e.getMeasure())
                .build();
    }

    private IngredientSimpleResponseDto createIngredientResponseDto(IngredientEntity i, List<Long> allowedIngredients) {
        return new IngredientSimpleResponseDto(i.getId(), i.getName(), allowedIngredients.contains(i.getId()));
    }


}
