/**
 * ***************************************************************************
 * Copyright (c) 2010 Qcadoo Limited
 * Project: Qcadoo MES
 * Version: 1.4
 *
 * This file is part of Qcadoo.
 *
 * Qcadoo is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * ***************************************************************************
 */
package com.qcadoo.mes.cmmsMachineParts.states;

import com.google.common.collect.Lists;
import com.qcadoo.mes.basic.ParameterService;
import com.qcadoo.mes.basic.constants.StaffFields;
import com.qcadoo.mes.cmmsMachineParts.constants.CmmsMachinePartsConstants;
import com.qcadoo.mes.cmmsMachineParts.constants.MaintenanceEventFields;
import com.qcadoo.mes.cmmsMachineParts.constants.MaintenanceEventType;
import com.qcadoo.mes.cmmsMachineParts.constants.ParameterFieldsCMP;
import com.qcadoo.mes.cmmsMachineParts.constants.StaffWorkTimeFields;
import com.qcadoo.mes.states.StateChangeContext;
import com.qcadoo.mes.states.messages.constants.StateMessageType;
import com.qcadoo.model.api.DataDefinitionService;
import com.qcadoo.model.api.Entity;
import com.qcadoo.model.api.NumberService;
import com.qcadoo.model.api.search.SearchQueryBuilder;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Seconds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MaintenanceEventStateValidationService {

    @Autowired private ParameterService parameterService;

    @Autowired private DataDefinitionService dataDefinitionService;

    @Autowired private NumberService numberService;

    public void validationOnInProgress(final StateChangeContext stateChangeContext) {
        Entity event = stateChangeContext.getOwner();
        checkIfPersonReceivingIsSet(event, stateChangeContext);
        checkIfFaultTypeSet(event, stateChangeContext);
    }

    public void validationOnEdited(final StateChangeContext stateChangeContext) {
        Entity event = stateChangeContext.getOwner();
        checkIfFaultTypeSet(event, stateChangeContext);
    }

    public void validationOnClosed(final StateChangeContext stateChangeContext) {
        Entity event = stateChangeContext.getOwner();
        checkIfSolutionDescriptionIsSet(event, stateChangeContext);
        checkIfWorkerTimeIsFilled(event, stateChangeContext);
        checkWorkerTimesDeviation(event, stateChangeContext);
        checkSourceCost(event, stateChangeContext);
        checkIfFaultTypeSet(event, stateChangeContext);
    }

    private void checkSourceCost(final Entity event, StateChangeContext stateChangeContext) {
        if (event.getBelongsToField(MaintenanceEventFields.SOURCE_COST) == null) {
            stateChangeContext.addFieldValidationError(MaintenanceEventFields.SOURCE_COST,
                    "cmmsMachineParts.maintenanceEvent.state.fieldRequired");
        }
    }

    public void validationOnRevoked(final StateChangeContext stateChangeContext) {
    }

    public void validationOnPlanned(final StateChangeContext stateChangeContext) {
        Entity event = stateChangeContext.getOwner();
        checkIfFaultTypeSet(event, stateChangeContext);
    }

    public void validationOnAcceptedd(final StateChangeContext stateChangeContext) {
        Entity event = stateChangeContext.getOwner();
        checkIfFaultTypeSet(event, stateChangeContext);
    }

    private void checkIfPersonReceivingIsSet(Entity event, StateChangeContext stateChangeContext) {
        if (event.getBelongsToField(MaintenanceEventFields.PERSON_RECEIVING) == null) {
            stateChangeContext.addFieldValidationError(MaintenanceEventFields.PERSON_RECEIVING,
                    "cmmsMachineParts.maintenanceEvent.state.fieldRequired");
            stateChangeContext.addValidationError("cmmsMachineParts.maintenanceEvent.state.workerNotFound");
        }
    }

    private void checkIfSolutionDescriptionIsSet(Entity event, StateChangeContext stateChangeContext) {
        if (StringUtils.isEmpty(event.getStringField(MaintenanceEventFields.SOLUTION_DESCRIPTION))) {
            stateChangeContext.addFieldValidationError(MaintenanceEventFields.SOLUTION_DESCRIPTION,
                    "cmmsMachineParts.maintenanceEvent.state.fieldRequired");
        }
    }

    private void checkIfWorkerTimeIsFilled(Entity event, StateChangeContext stateChangeContext) {
        if (event.getHasManyField(MaintenanceEventFields.STAFF_WORK_TIEMS).isEmpty()) {
            stateChangeContext.addValidationError("cmmsMachineParts.maintenanceEvent.state.noWorkersTimeEntry");
        }
    }

    private void checkWorkerTimesDeviation(Entity event, StateChangeContext stateChangeContext) {

        Optional<Integer> progressTime = getProgressTime(event);
        Optional<BigDecimal> possibleDeviationPercent = getPossibleDeviationFromParameters();
        if (!progressTime.isPresent() || !possibleDeviationPercent.isPresent()
                || MaintenanceEventType.from(event).compareTo(MaintenanceEventType.PROPOSAL) == 0) {
            return;
        }
        Integer possibleDeviation = calculatePossibleDeviation(progressTime.get(), possibleDeviationPercent.get());
        Map<Entity, Integer> groupedWorkTimes = getGroupedStaffWorkTimes(event);
        List<String> workersWithIncorrectTime = Lists.newArrayList();
        for (Map.Entry<Entity, Integer> entry : groupedWorkTimes.entrySet()) {
            Integer diff = entry.getValue() - progressTime.get();
            if (diff > possibleDeviation) {
                workersWithIncorrectTime.add(entry.getKey().getStringField(StaffFields.NAME) + " " + entry.getKey().getStringField(StaffFields.SURNAME));
            }
        }
        if (!workersWithIncorrectTime.isEmpty()) {
            stateChangeContext
                    .addMessage("cmmsMachineParts.maintenanceEvent.state.tooLongWorkersTime", StateMessageType.INFO, false,
                            workersWithIncorrectTime.stream().collect(Collectors.joining(", ")));
        }

    }

    private Integer calculatePossibleDeviation(Integer progressTime, BigDecimal possibleDeviationPercent) {
        BigDecimal percent = possibleDeviationPercent.divide(new BigDecimal(100), numberService.getMathContext());
        BigDecimal possibleDeviation = percent.multiply(new BigDecimal(progressTime));
        return possibleDeviation.intValue();
    }

    private Map<Entity, Integer> getGroupedStaffWorkTimes(Entity event) {
        List<Entity> staffWorkTimes = event.getHasManyField(MaintenanceEventFields.STAFF_WORK_TIMES);
        Function<Entity, Entity> toWorker = entity -> entity.getBelongsToField(StaffWorkTimeFields.WORKER);
        ToIntFunction<Entity> toInt = entity -> entity.getIntegerField(StaffWorkTimeFields.LABOR_TIME);

        return staffWorkTimes.stream().collect(Collectors.groupingBy(toWorker, Collectors.summingInt(toInt)));
    }

    private Optional<BigDecimal> getPossibleDeviationFromParameters() {
        return Optional.ofNullable(parameterService.getParameter().getDecimalField(ParameterFieldsCMP.POSSIBLE_WORK_TIME_DEVIATION));
    }

    private Optional<Integer> getProgressTime(Entity event) {
        StringBuilder hqlForStart = new StringBuilder();
        hqlForStart.append("select max(dateAndTime) as date from #cmmsMachineParts_maintenanceEventStateChange sc ");
        hqlForStart.append("where sc.maintenanceEvent = :eventId and sc.status = '03successful' ");
        hqlForStart.append("and sc.targetState = '02inProgress'");
        SearchQueryBuilder query = dataDefinitionService
                .get(CmmsMachinePartsConstants.PLUGIN_IDENTIFIER, CmmsMachinePartsConstants.MODEL_MAINTENANCE_EVENT_STATE_CHANGE).find(hqlForStart.toString());
        query.setLong("eventId", event.getId());
        Date start = query.setMaxResults(1).uniqueResult().getDateField("date");

        StringBuilder hqlForEnd = new StringBuilder();
        hqlForEnd.append("select max(dateAndTime) as date from #cmmsMachineParts_maintenanceEventStateChange sc ");
        hqlForEnd.append("where sc.maintenanceEvent = :eventId and sc.status = '03successful' ");
        hqlForEnd.append("and sc.targetState = '03edited'");
        query = dataDefinitionService.get(CmmsMachinePartsConstants.PLUGIN_IDENTIFIER, CmmsMachinePartsConstants.MODEL_MAINTENANCE_EVENT_STATE_CHANGE)
                .find(hqlForEnd.toString());
        query.setLong("eventId", event.getId());

        Date end = query.setMaxResults(1).uniqueResult().getDateField("date");

        if (start != null && end != null && start.before(end)) {
            Seconds seconds = Seconds.secondsBetween(new DateTime(start), new DateTime(end));
            return Optional.of(Integer.valueOf(seconds.getSeconds()));
        }
        return Optional.empty();
    }


    private void checkIfFaultTypeSet(Entity event, StateChangeContext stateChangeContext) {
        if (event.getBelongsToField(MaintenanceEventFields.FAULT_TYPE) == null) {
            stateChangeContext.addFieldValidationError(MaintenanceEventFields.FAULT_TYPE,
                    "cmmsMachineParts.maintenanceEvent.state.fieldRequired");
        }
    }
}
