package de.avatic.lcc.service.calculation.execution;

import de.avatic.lcc.dto.calculation.execution.*;
import de.avatic.lcc.model.db.calculations.CalculationJob;
import de.avatic.lcc.model.db.calculations.CalculationJobState;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.model.db.properties.PropertySet;
import de.avatic.lcc.model.db.rates.ValidityPeriod;
import de.avatic.lcc.repositories.calculation.CalculationJobDestinationRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRepository;
import de.avatic.lcc.repositories.calculation.CalculationJobRouteSectionRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.properties.PropertySetRepository;
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
import de.avatic.lcc.service.precalculation.PostCalculationCheckService;
import de.avatic.lcc.service.precalculation.PreCalculationCheckService;
import de.avatic.lcc.service.users.AuthorizationService;
import de.avatic.lcc.util.exception.internalerror.PremiseValidationError;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class CalculationJobProcessorManagementService {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CalculationJobProcessorManagementService.class);
    private final AuthorizationService authorizationService;
    private final PremiseRepository premiseRepository;
    private final PropertySetRepository propertySetRepository;
    private final ValidityPeriodRepository validityPeriodRepository;
    private final PreCalculationCheckService preCalculationCheckService;
    private final CalculationJobRepository calculationJobRepository;
    private final CalculationExecutionService calculationExecutionService;
    private final PostCalculationCheckService postCalculationCheckService;
    private final CalculationJobDestinationRepository calculationJobDestinationRepository;
    private final CalculationJobRouteSectionRepository calculationJobRouteSectionRepository;

    public CalculationJobProcessorManagementService(AuthorizationService authorizationService, PremiseRepository premiseRepository, PropertySetRepository propertySetRepository, ValidityPeriodRepository validityPeriodRepository, PreCalculationCheckService preCalculationCheckService, CalculationJobRepository calculationJobRepository, CalculationExecutionService calculationExecutionService, PostCalculationCheckService postCalculationCheckService, CalculationJobDestinationRepository calculationJobDestinationRepository, CalculationJobRouteSectionRepository calculationJobRouteSectionRepository) {
        this.authorizationService = authorizationService;
        this.premiseRepository = premiseRepository;
        this.propertySetRepository = propertySetRepository;
        this.validityPeriodRepository = validityPeriodRepository;
        this.preCalculationCheckService = preCalculationCheckService;
        this.calculationJobRepository = calculationJobRepository;
        this.calculationExecutionService = calculationExecutionService;
        this.postCalculationCheckService = postCalculationCheckService;
        this.calculationJobDestinationRepository = calculationJobDestinationRepository;
        this.calculationJobRouteSectionRepository = calculationJobRouteSectionRepository;
    }


    public CalculationStartResponseDTO startCalculation(CalculationStartRequestDTO request) {
        var admin = authorizationService.isSuper();
        var userId = authorizationService.getUserId();

        if (!admin)
            premiseRepository.checkOwner(request.getPremiseIds(), userId);


        Optional<PropertySet> validSet;
        Optional<ValidityPeriod> validPeriod;

        if (request.getDate() == null) {
            validSet = propertySetRepository.getValidSet();
            validPeriod = validityPeriodRepository.getValidPeriod();
        } else {
            validSet = propertySetRepository.getByDate(request.getDate());
            validPeriod = validityPeriodRepository.getByDate(request.getDate());
        }

        boolean multiRequest = request.getPremiseIds().size() > 1;
        request.getPremiseIds().forEach(premiseId -> preCalculationCheckService.doPrecheck(premiseId, validSet, validPeriod, request.getDate(), multiRequest));


        if (validSet.isEmpty() || validPeriod.isEmpty())
            throw new PremiseValidationError("No rates available for this date - please contact your administrator.");

        var validSetId = validSet.get().getId();
        var validPeriodId = validPeriod.get().getId();

        var calculationIds = new ArrayList<Integer>();

        request.getPremiseIds().forEach(p -> {

            var existingJob = calculationJobRepository.findJob(p, validSetId, validPeriodId);

            if (existingJob.isPresent()) {
                if (CalculationJobState.EXCEPTION == existingJob.get().getJobState()) {
                    calculationIds.add(existingJob.get().getId());
                }
            } else {
                CalculationJob job = new CalculationJob();

                job.setPremiseId(p);
                job.setJobState(CalculationJobState.CREATED);
                job.setCalculationDate(java.time.LocalDateTime.now());
                job.setUserId(userId);
                job.setPropertySetId(validSetId);
                job.setValidityPeriodId(validPeriodId);
                calculationIds.add(calculationJobRepository.insert(job));
            }

        });

        premiseRepository.setStatus(request.getPremiseIds(), PremiseState.COMPLETED);

        var dto = new CalculationStartResponseDTO();
        dto.setCalculationIds(calculationIds);
        return dto;
    }

    public CalculationProcessingOverviewDTO getCalculationOverview() {
        var dto = new CalculationProcessingOverviewDTO();

        var userId = authorizationService.getUserId();

        int completed = premiseRepository.getPremiseCompletedCountByUserId(userId);
        int draft = premiseRepository.getPremiseDraftCountByUserId(userId);

        int running = calculationJobRepository.getSelfScheduledJobCountByUserId(userId);
        int failed = calculationJobRepository.getFailedJobByUserId(userId);

        dto.setFailed(failed);
        dto.setDrafts(draft);
        dto.setRunning(running);
        dto.setCompleted(completed);

        return dto;
    }

    public CalculationProcessingStateResponseDTO getCalculationStatus(CalculationProcessingStateRequestDTO requestDTO) {
        var ids = requestDTO.getCalculationIds();


        if (ids != null && !ids.isEmpty()) {
            var states = calculationJobRepository.getStateFor(ids).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().name()));

            var dto = new CalculationProcessingStateResponseDTO();
            dto.setStates(states);
            return dto;
        }

        return new CalculationProcessingStateResponseDTO();
    }
}
