package com.nonononoki.alovoa.html;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.nonononoki.alovoa.Tools;
import com.nonononoki.alovoa.component.ExceptionHandler;
import com.nonononoki.alovoa.component.TextEncryptorConverter;
import com.nonononoki.alovoa.entity.User;
import com.nonononoki.alovoa.entity.user.*;
import com.nonononoki.alovoa.model.AlovoaException;
import com.nonononoki.alovoa.model.ConversationDto;
import com.nonononoki.alovoa.model.NotificationDto;
import com.nonononoki.alovoa.model.UserDto;
import com.nonononoki.alovoa.repo.*;
import com.nonononoki.alovoa.service.AuthService;
import com.nonononoki.alovoa.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.ModelAndView;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@RestController
@RequestMapping("/api/v1/resource")
public class ApiResource {

    @Autowired
    private UserRepository userRepo;

    @Autowired
    private ProfileResource profileResource;

    @Autowired
    private AdminSearchResource searchResource;

    @Autowired
    private GenderRepository genderRepo;

    @Autowired
    private UserIntentionRepository userIntentionRepo;

    @Autowired
    private ConversationRepository conversationRepo;

    @Autowired
    private UserBlockRepository userBlockRepo;

    @Autowired
    private AuthService authService;

    @Autowired
    private UserService userService;

    @Autowired
    private TextEncryptorConverter textEncryptor;

    private static final long MAX_RESULTS = 50;

    @Value("${app.image.max-size}")
    private int mediaMaxSize;
    @Value("${app.interest.max}")
    private int interestMaxSize;
    @Value("${app.search.ignore-intention}")
    private boolean ignoreIntention;

    @GetMapping("/donate")
    public Map<String, Object> resourceDonate() throws InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        ModelMap map = new ModelMap();
        User user = authService.getCurrentUser(true);
        map.addAttribute("user", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build()));
        return map;
    }

    @GetMapping("/chats")
    public Map<String, Object> resourceChats()
            throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        ModelMap map = new ModelMap();
        User user = authService.getCurrentUser(true);
        user.getDates().setMessageCheckedDate(new Date());
        userRepo.saveAndFlush(user);
        List<ConversationDto> convos = new ArrayList<>();
        List<Conversation> conversations = conversationRepo.findByUsers_Id(user.getId());
        for (Conversation c : conversations) {
            if (!c.isBlocked(userBlockRepo)) {
                convos.add(ConversationDto.conversationToDto(c, user, userService));
            }
        }

        convos.sort((ConversationDto a, ConversationDto b) -> b.getLastUpdated().compareTo(a.getLastUpdated()));

        map.addAttribute("conversations", convos);
        map.addAttribute("user", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build()));
        return map;
    }

    @GetMapping("/chats/{id}")
    public Map<String, Object> resourceChatsDetail(@PathVariable long id) throws JsonProcessingException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        ModelMap map = new ModelMap();
        User user = authService.getCurrentUser(true);
        Conversation c = conversationRepo.findById(id).orElse(null);

        if (c == null) {
            throw new AlovoaException("conversation_not_found");
        }

        if (!c.containsUser(user)) {
            throw new AlovoaException("user_not_in_conversation");
        }

        User u = c.getPartner(user);

        map.addAttribute("user", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build()));
        map.addAttribute("convoId", id);
        map.addAttribute("partner", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(u).userService(userService).build()));

        c.setLastOpened(new Date());
        conversationRepo.saveAndFlush(c);
        return map;
    }

    @GetMapping("/alerts")
    public Map<String, Object> resourceNotification() throws InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        ModelMap map = new ModelMap();
        User user = authService.getCurrentUser(true);
        user.getDates().setNotificationCheckedDate(new Date());
        userRepo.saveAndFlush(user);
        List<UserNotification> nots = user.getNotifications();
        List<NotificationDto> notifications = new ArrayList<>();
        for (UserNotification n : nots) {
            boolean blockedMe = user.getBlockedByUsers().stream()
                    .anyMatch(b -> b.getUserFrom().getId().equals(n.getUserFrom().getId()));
            boolean blockedYou = user.getBlockedUsers().stream()
                    .anyMatch(b -> b.getUserTo().getId().equals(n.getUserFrom().getId()));
            boolean matched = user.getLikes().stream().anyMatch(l -> n.getUserFrom().getId().equals(l.getUserTo().getId()));

            if (!blockedMe && !blockedYou && !matched) {
                NotificationDto dto = NotificationDto.notificationToNotificationDto(n, user, userService, textEncryptor, ignoreIntention);
                notifications.add(dto);
            }
        }

        notifications.sort((NotificationDto a, NotificationDto b) -> b.getDate().compareTo(a.getDate()));

        map.addAttribute("notifications", notifications);
        map.addAttribute("user", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build()));
        return map;
    }

    @GetMapping("/profile")
    public Map<String, Object> resourceProfile() throws InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        return profileResource.profile().getModel();
    }

    @GetMapping("/profile/view/{uuid}")
    public Map<String, Object> resourceProfileView(@PathVariable UUID uuid) throws
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {

        User userView = userService.findUserByUuid(uuid);
        User user = authService.getCurrentUser(true);

        if (user.getId().equals(userView.getId())) {
            throw new AlovoaException(ExceptionHandler.USER_NOT_FOUND);
        }

        if (userView.getBlockedUsers().stream().filter(o -> o.getUserTo() != null).anyMatch(o -> o.getUserTo().getId().equals(user.getId()))) {
            throw new AlovoaException(ExceptionHandler.USER_NOT_FOUND);
        }

        ModelMap map = new ModelMap();

        userView = userRepo.saveAndFlush(userView);

        UserDto userDto = UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(userView).userService(userService).build());
        UserDto currUserDto = UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build());

        map.addAttribute("user", userDto);
        map.addAttribute("currUser", currUserDto);
        map.addAttribute("compatible", Tools.usersCompatible(user, userView, ignoreIntention));

        int age = Tools.calcUserAge(user);
        boolean isLegal = age >= Tools.AGE_LEGAL;
        map.addAttribute("isLegal", isLegal);

        return map;
    }

    @GetMapping("/search")
    public Map<String, Object> resourceSearch() throws InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        return searchResource.search().getModel();
    }

    @GetMapping("/user/onboarding")
    public Map<String, Object> userOnboarding() throws AlovoaException {
        User user = authService.getCurrentUser(true);
        int age = Tools.calcUserAge(user);
        boolean isLegal = age >= Tools.AGE_LEGAL;
        ModelMap mav = new ModelMap();
        mav.addAttribute("genders", genderRepo.findAll());
        mav.addAttribute("intentions", userIntentionRepo.findAll());
        mav.addAttribute("isLegal", isLegal);
        mav.addAttribute("mediaMaxSize", mediaMaxSize);
        mav.addAttribute("interestMaxSize", interestMaxSize);
        return mav;
    }

    @GetMapping("/blocked-users")
    public Map<String, Object> blockedUsers() throws JsonProcessingException, InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        User user = authService.getCurrentUser(true);
        ModelMap map = new ModelMap();
        List<UserBlock> userBlocks = user.getBlockedUsers();
        List<User> blockedUsers = userBlocks.stream().sorted(Comparator.comparing(UserBlock::getDate).reversed())
                .limit(MAX_RESULTS).map(UserBlock::getUserTo).toList();
        List<UserDto> users = new ArrayList<>();
        for (User u : blockedUsers) {
            users.add(UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                    .currentUser(user).user(u).userService(userService).build()));
        }
        map.addAttribute("users", users);
        map.addAttribute("user", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build()));
        return map;
    }

    @GetMapping("/liked-users")
    public Map<String, Object> likedUsers() throws InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        User user = authService.getCurrentUser(true);
        ModelMap map = new ModelMap();
        List<UserLike> userLikes = user.getLikes();
        List<User> likedUsers = userLikes.stream().sorted(Comparator.comparing(UserLike::getDate).reversed())
                .limit(MAX_RESULTS).map(UserLike::getUserTo).toList();
        List<UserDto> users = new ArrayList<>();
        for (User u : likedUsers) {
            users.add(UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                    .currentUser(user).user(u).userService(userService).build()));
        }
        map.addAttribute("users", users);
        map.addAttribute("user", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build()));
        return map;
    }

    @GetMapping("/disliked-users")
    public Map<String, Object> dislikedUsers() throws InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, UnsupportedEncodingException, AlovoaException {
        User user = authService.getCurrentUser(true);
        ModelMap map = new ModelMap();
        List<UserHide> userDislikes = user.getHiddenUsers();
        List<User> dislikedUsers = userDislikes.stream().sorted(Comparator.comparing(UserHide::getDate).reversed())
                .limit(MAX_RESULTS).map(UserHide::getUserTo).toList();
        List<UserDto> users = new ArrayList<>();
        for (User u : dislikedUsers) {
            users.add(UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                    .currentUser(user).user(u).userService(userService).build()));
        }
        map.addAttribute("users", users);
        map.addAttribute("user", UserDto.userToUserDto(UserDto.DtoBuilder.builder().ignoreIntention(ignoreIntention)
                .currentUser(user).user(user).userService(userService).build()));
        return map;
    }
}
