package ru.ulstu.paper.service;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.deadline.service.DeadlineService;
import ru.ulstu.file.service.FileService;
import ru.ulstu.paper.model.Paper;
import ru.ulstu.paper.model.PaperDto;
import ru.ulstu.paper.model.PaperFilterDto;
import ru.ulstu.paper.repository.PaperRepository;
import ru.ulstu.user.model.User;
import ru.ulstu.user.service.UserService;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springframework.util.ObjectUtils.isEmpty;
import static ru.ulstu.core.util.StreamApiUtils.convert;
import static ru.ulstu.paper.model.Paper.PaperStatus.ATTENTION;
import static ru.ulstu.paper.model.Paper.PaperStatus.COMPLETED;
import static ru.ulstu.paper.model.Paper.PaperStatus.DRAFT;
import static ru.ulstu.paper.model.Paper.PaperStatus.FAILED;
import static ru.ulstu.paper.model.Paper.PaperStatus.ON_PREPARATION;

@Service
public class PaperService {
    private final static int MAX_DISPLAY_SIZE = 40;

    private final PaperNotificationService paperNotificationService;
    private final PaperRepository paperRepository;
    private final UserService userService;
    private final DeadlineService deadlineService;
    private final FileService fileService;

    public PaperService(PaperRepository paperRepository,
                        FileService fileService,
                        PaperNotificationService paperNotificationService,
                        UserService userService,
                        DeadlineService deadlineService) {
        this.paperRepository = paperRepository;
        this.fileService = fileService;
        this.paperNotificationService = paperNotificationService;
        this.userService = userService;
        this.deadlineService = deadlineService;
    }

    public List<Paper> findAll() {
        return sortPapers(paperRepository.findAll());
    }

    public List<PaperDto> findAllDto() {
        List<PaperDto> papers = convert(findAll(), PaperDto::new);
        papers.forEach(paperDto -> paperDto.setTitle(StringUtils.abbreviate(paperDto.getTitle(), MAX_DISPLAY_SIZE)));
        return papers;
    }

    public List<PaperDto> findAllActive() {
        return findAllDto()
                .stream()
                .filter(paper -> paper.getStatus() != COMPLETED && paper.getStatus() != FAILED)
                .collect(Collectors.toList());
    }

    public PaperDto findOneDto(Integer id) {
        return new PaperDto(paperRepository.findOne(id));
    }

    @Transactional
    public Integer create(PaperDto paperDto) throws IOException {
        Paper newPaper = copyFromDto(new Paper(), paperDto);
        newPaper = paperRepository.save(newPaper);
        paperNotificationService.sendCreateNotification(newPaper);
        return newPaper.getId();
    }

    private Paper copyFromDto(Paper paper, PaperDto paperDto) throws IOException {
        paper.setComment(paperDto.getComment());
        paper.setCreateDate(paper.getCreateDate() == null ? new Date() : paper.getCreateDate());
        paper.setLocked(paperDto.getLocked());
        paper.setStatus(paperDto.getStatus() == null ? DRAFT : paperDto.getStatus());
        paper.setTitle(paperDto.getTitle());
        paper.setUpdateDate(new Date());
        paper.setDeadlines(deadlineService.saveOrCreate(paperDto.getDeadlines()));
        if (paperDto.getTmpFileName() != null) {
            paper.setFileData(fileService.createFileFromTmp(paperDto.getTmpFileName()));
        }
        paper.getAuthors().clear();
        if (paperDto.getAuthorIds() != null && !paperDto.getAuthorIds().isEmpty()) {
            paperDto.getAuthorIds().forEach(authorIds -> paper.getAuthors().add(userService.findById(authorIds)));
        }
        return paper;
    }

    @Transactional
    public Integer update(PaperDto paperDto) throws IOException {
        Paper paper = paperRepository.findOne(paperDto.getId());
        Paper.PaperStatus oldStatus = paper.getStatus();
        Set<User> oldAuthors = new HashSet<>(paper.getAuthors());
        if (paperDto.getTmpFileName() != null && paper.getFileData() != null) {
            fileService.deleteFile(paper.getFileData());
        }
        paperRepository.save(copyFromDto(paper, paperDto));

        paper.getAuthors().forEach(author -> {
            if (!oldAuthors.contains(author)) {
                paperNotificationService.sendCreateNotification(paper);
            }
        });

        if (paper.getStatus() != oldStatus) {
            paperNotificationService.statusChangeNotification(paper, oldStatus);
        }

        return paper.getId();
    }

    @Transactional
    public void delete(Integer paperId) throws IOException {
        Paper paper = paperRepository.findOne(paperId);
        if (paper.getFileData() != null) {
            fileService.deleteFile(paper.getFileData());
        }
        paperRepository.delete(paper);
    }

    public List<Paper.PaperStatus> getPaperStatuses() {
        return Arrays.asList(Paper.PaperStatus.values());
    }

    @Transactional
    public Paper create(String title, User user, Date deadlineDate) {
        Paper paper = new Paper();
        paper.setTitle(title);
        paper.getAuthors().add(user);
        paper.getDeadlines().add(new Deadline(deadlineDate, "первый дедлайн"));
        paper.setCreateDate(new Date());
        paper.setUpdateDate(new Date());
        paper.setStatus(DRAFT);
        paper = paperRepository.save(paper);

        paperNotificationService.sendCreateNotification(paper);

        return paper;
    }

    public List<PaperDto> filter(PaperFilterDto filterDto) {
        return convert(sortPapers(paperRepository.filter(
                filterDto.getFilterAuthorId() == null ? null : userService.findById(filterDto.getFilterAuthorId()),
                filterDto.getYear())), PaperDto::new);
    }

    private List<Paper> sortPapers(List<Paper> papers) {
        return papers.stream().sorted((paper1, paper2) -> {
            int statusCompareResult =
                    Integer.valueOf(Arrays.asList(Paper.PaperStatus.values()).indexOf(paper1.getStatus()))
                            .compareTo(Arrays.asList(Paper.PaperStatus.values()).indexOf(paper2.getStatus()));
            if (statusCompareResult != 0) {
                return statusCompareResult;
            }
            return paper1.getTitle().compareTo(paper2.getTitle());
        }).collect(Collectors.toList());
    }

    public PaperDto findPaper(int id) {
        return new PaperDto(paperRepository.getOne(id));
    }

    public void closeFailedPapers() {
        List<Paper> papers = paperRepository.findAll()
                .stream()
                .filter(paper -> paper.lastDeadlineFailed()
                        && (paper.getStatus() == ON_PREPARATION
                        || paper.getStatus() == DRAFT
                        || paper.getStatus() == ATTENTION))
                .collect(Collectors.toList());
        papers.forEach(paper -> {
            Paper.PaperStatus oldStatus = paper.getStatus();
            paper.setStatus(Paper.PaperStatus.FAILED);
            paperRepository.save(paper);
            paperNotificationService.sendFailedNotification(paper, oldStatus);
        });
    }

    public void save(PaperDto paperDto) throws IOException {
        if (isEmpty(paperDto.getId())) {
            create(paperDto);
        } else {
            update(paperDto);
        }
    }

    public PaperDto findById(Integer paperId) {
        return new PaperDto(paperRepository.findOne(paperId));
    }

    public List<User> getPaperAuthors() {
        return userService.findAll();
    }
}
