package com.sn.sowsysrestapi.domain.service;

import com.sn.sowsysrestapi.api.dto.ReportDTO;
import com.sn.sowsysrestapi.api.dto.ReportDirectoryDTO;
import com.sn.sowsysrestapi.domain.entity.CompletedTask;
import com.sn.sowsysrestapi.domain.entity.CurrentTask;
import com.sn.sowsysrestapi.domain.entity.Report;
import com.sn.sowsysrestapi.domain.entity.ReportDirectory;
import com.sn.sowsysrestapi.domain.exception.EntityInUseException;
import com.sn.sowsysrestapi.domain.exception.ReportNotFoundException;
import com.sn.sowsysrestapi.domain.repository.ReportDirectoryRepo;
import com.sn.sowsysrestapi.domain.repository.ReportRepo;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Service
public class ReportService {

    public static final String MSG_ENTITY_IN_USE = "Report with ID %d can't be deleted. Reason: Other entity is using this entity.";

    @Autowired
    private ReportRepo reportRepo;

    @Autowired
    private ReportDirectoryService reportDirectoryService;

    @Autowired
    private ReportDirectoryRepo reportDirectoryRepo;

    @Autowired
    private ModelMapper modelMapper;

    @Transactional
    public Report save(Report report) {

        String reportDirectoryOwnerCode = report.getReportDirectory().getOwnerCode();
        ReportDirectory reportDirectory = reportDirectoryService.findOrFailByOwnerCode(reportDirectoryOwnerCode);

        report.setReportDirectory(reportDirectory);

        return reportRepo.save(report);
    }

    public void delete(Long id) {

        try {
            reportRepo.deleteById(id);
            reportRepo.flush();

        } catch (EmptyResultDataAccessException e) {
            throw new ReportNotFoundException(id);

        } catch (DataIntegrityViolationException e) {
            throw new EntityInUseException(
                    String.format(MSG_ENTITY_IN_USE, id));
        }

    }

    public Report findOrFail(Long reportId) {

        return reportRepo.findById(reportId)
                .orElseThrow(() -> new ReportNotFoundException(reportId));

    }

    // method to calculate the total worked hours for Current Tasks on the report
    public double calculateTotalHoursSpentCurrentTasks(Long reportId) {

        Report report = findOrFail(reportId);

        return report.getCurrentTaskList()
                .stream()
                .map(CurrentTask::getHoursSpent)
                .reduce(0.0, Double::sum);

    }

    // method to calculate the total worked hours for Current Tasks on the report
    public double calculateTotalHoursSpentCompletedTasks(Long reportId) {

        Report report = findOrFail(reportId);

        return report.getCompletedTaskList()
                .stream()
                .map(CompletedTask::getHoursSpent)
                .reduce(0.0, Double::sum);

    }

    // method to calculate the total worked hours for the report (completed tasks + current tasks)
    public double calculateTotalWorkedHoursForTheReport(Long reportId) {

        return calculateTotalHoursSpentCurrentTasks(reportId) + calculateTotalHoursSpentCompletedTasks(reportId);

    }

    public int getCurrentTasksQuantity(Long reportId) {

        Report report = findOrFail(reportId);

        return report.getCurrentTaskList()
                .size();

    }

    public int getCompletedTasksQuantity(Long reportId) {

        Report report = findOrFail(reportId);

        return report.getCompletedTaskList()
                .size();

    }

    public void updateHoursAndQtyOnReport(Long reportId) {

        Report report = findOrFail(reportId);

        report.setTotalWorkedHoursCurrentTasks(calculateTotalHoursSpentCurrentTasks(reportId));
        report.setTotalWorkedHoursCompletedTasks(calculateTotalHoursSpentCompletedTasks(reportId));
        report.setTotalWorkedHours(calculateTotalWorkedHoursForTheReport(reportId));
        report.setQtyCurrentTasks(getCurrentTasksQuantity(reportId));
        report.setQtyCompletedTasks(getCompletedTasksQuantity(reportId));

    }

    public List<ReportDTO> findOrFailByOwnerCodePageable(String ownerCode, Pageable page) {

        ReportDirectory reportDirectory = reportDirectoryService.findOrFail(ownerCode);

        Optional<List<Report>> reportList = reportRepo.findPageableByReportDirectoryId(
                reportDirectory.getId(), page);

        List<ReportDTO> reportDTOList = new ArrayList<>();

        if (reportList.isPresent()) {
            modelMapper.map(reportDTOList, reportList);
        } else {
            return Collections.emptyList();
        }

        return reportDTOList;

    }

}
