/**
 * ***************************************************************************
 * 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.states;

import static com.qcadoo.mes.states.constants.StateChangeStatus.FAILURE;
import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import java.util.List;
import java.util.Map;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.qcadoo.mes.states.constants.StateChangeStatus;
import com.qcadoo.mes.states.exception.StateChangeException;
import com.qcadoo.mes.states.messages.MessageService;
import com.qcadoo.model.api.Entity;
import com.qcadoo.model.api.validators.ErrorMessage;

public class StateChangeContextTest extends StateChangeTest {

    @Mock
    private MessageService messageService;

    @Mock
    private Entity savedOwner;

    private StateChangeEntityDescriber describer;

    private StateChangeContext stateChangeContext;

    private static final String FIELD_1_NAME = "field_1";

    private static final String FIELD_1_MESSAGE_1 = "field_1_message_1";

    private static final String GLOBAL_MESSAGE_1 = "global_message_1";

    @Before
    public final void init() {
        MockitoAnnotations.initMocks(this);
        describer = new MockStateChangeDescriber(stateChangeDD);
        stubStateChangeEntity(describer);
        stubOwner();
        stubBelongsToField(stateChangeEntity, describer.getOwnerFieldName(), owner);
        stateChangeContext = new StateChangeContextImpl(stateChangeEntity, describer, messageService);
    }

    @Test
    public final void shouldSave() {
        // when
        stateChangeContext.save();

        // then
        verify(stateChangeDD, atLeastOnce()).save(stateChangeEntity);
        verify(messageService, never()).addValidationError(Mockito.eq(stateChangeContext), Mockito.anyString(),
                Mockito.anyString(), Mockito.any(String[].class));
        verify(messageService, never()).addValidationError(Mockito.eq(stateChangeContext), Mockito.eq((String) null),
                Mockito.anyString(), Mockito.any(String[].class));
        verify(stateChangeEntity, never()).setField(describer.getStatusFieldName(), FAILURE.getStringValue());
    }

    @Test
    public final void shouldThrowExceptionIfConstructorGetInvalidStateChangeEntity() {
        // given
        given(stateChangeEntity.isValid()).willReturn(false);

        // when
        try {
            new StateChangeContextImpl(stateChangeEntity, describer, messageService);
            Assert.fail();
        } catch (IllegalArgumentException e) {
        }
    }

    @Test
    public final void shouldMarkExistingDatabaseEntityAsInvalidIfConstructorGetInvalidStateChangeEntity() {
        // given
        final Entity existingStateChangeEntity = mock(Entity.class);
        given(stateChangeEntity.isValid()).willReturn(false);
        given(stateChangeEntity.getId()).willReturn(1L);
        given(stateChangeDD.get(Mockito.any(Long.class))).willReturn(existingStateChangeEntity);
        given(existingStateChangeEntity.isValid()).willReturn(true);

        final Map<String, ErrorMessage> fieldErrorsMap = Maps.newHashMap();
        final ErrorMessage fieldErrorMessage = buildErrorMessage(FIELD_1_MESSAGE_1);
        fieldErrorsMap.put(FIELD_1_NAME, fieldErrorMessage);
        given(stateChangeEntity.getErrors()).willReturn(fieldErrorsMap);

        final List<ErrorMessage> globalErrors = Lists.newArrayList();
        final ErrorMessage globalErrorMessage = buildErrorMessage(GLOBAL_MESSAGE_1);
        globalErrors.add(globalErrorMessage);
        given(stateChangeEntity.getGlobalErrors()).willReturn(globalErrors);

        // when
        new StateChangeContextImpl(stateChangeEntity, describer, messageService);

        // then
        verify(messageService).addValidationError(Mockito.any(StateChangeContext.class), Mockito.eq(FIELD_1_NAME),
                Mockito.eq(FIELD_1_MESSAGE_1));
        verify(messageService).addValidationError(Mockito.any(StateChangeContext.class), Mockito.eq((String) null),
                Mockito.eq(GLOBAL_MESSAGE_1));
        verify(existingStateChangeEntity).setField(describer.getStatusFieldName(), FAILURE.getStringValue());
    }

    @Test
    public final void shouldMarkEntityAsInvalidAndSetStateToFailureIfStateChangeEntityIsInvalidAfterSave() {
        // given
        final Entity savedStateChangeEntity = mock(Entity.class);
        given(stateChangeEntity.isValid()).willReturn(true);
        given(savedStateChangeEntity.isValid()).willReturn(true, false);
        given(stateChangeDD.save(stateChangeEntity)).willReturn(savedStateChangeEntity);

        final Map<String, ErrorMessage> fieldErrorsMap = Maps.newHashMap();
        final ErrorMessage fieldErrorMessage = buildErrorMessage(FIELD_1_MESSAGE_1);
        fieldErrorsMap.put(FIELD_1_NAME, fieldErrorMessage);
        given(savedStateChangeEntity.getErrors()).willReturn(fieldErrorsMap);

        final List<ErrorMessage> globalErrors = Lists.newArrayList();
        final ErrorMessage globalErrorMessage = buildErrorMessage(GLOBAL_MESSAGE_1);
        globalErrors.add(globalErrorMessage);
        given(savedStateChangeEntity.getGlobalErrors()).willReturn(globalErrors);

        // when
        stateChangeContext.save();

        // then
        verify(messageService).addValidationError(stateChangeContext, FIELD_1_NAME, FIELD_1_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(messageService).addValidationError(stateChangeContext, null, GLOBAL_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(stateChangeEntity).setField(describer.getStatusFieldName(), FAILURE.getStringValue());
    }

    @Test
    public final void shouldMarkEntityAsFailureAndRethrowExceptionIfOwnerValidatorThrowsException() {
        // given
        given(ownerDD.save(owner)).willThrow(new RuntimeException());

        try {
            // when
            stateChangeContext.setOwner(owner);
        } catch (StateChangeException e) {
            // then
            verify(stateChangeEntity).setField(describer.getStatusFieldName(), FAILURE.getStringValue());
        }
    }

    @Test
    public final void shouldSaveAndSetOwnerEntity() {
        // given
        given(owner.isValid()).willReturn(true);
        given(savedOwner.isValid()).willReturn(true);
        given(ownerDD.save(owner)).willReturn(savedOwner);

        // when
        stateChangeContext.setOwner(owner);

        // then
        verify(ownerDD).save(owner);
        verify(stateChangeEntity, never()).setField(describer.getOwnerFieldName(), owner);
        verify(stateChangeEntity).setField(describer.getOwnerFieldName(), savedOwner);
        verify(messageService, never()).addValidationError(Mockito.eq(stateChangeContext), Mockito.anyString(),
                Mockito.anyString(), Mockito.any(String[].class));
        verify(messageService, never()).addValidationError(Mockito.eq(stateChangeContext), Mockito.eq((String) null),
                Mockito.anyString(), Mockito.any(String[].class));
        verify(stateChangeEntity, never()).setField(describer.getStatusFieldName(), StateChangeStatus.FAILURE.getStringValue());
    }

    @Test
    public final void shouldNotSaveAndSetAlreadyInvalidOwnerEntity() {
        // given
        given(owner.isValid()).willReturn(false);
        given(ownerDD.save(owner)).willReturn(owner);

        final Map<String, ErrorMessage> fieldErrorsMap = Maps.newHashMap();
        final ErrorMessage fieldErrorMessage = buildErrorMessage(FIELD_1_MESSAGE_1);
        fieldErrorsMap.put(FIELD_1_NAME, fieldErrorMessage);
        given(owner.getErrors()).willReturn(fieldErrorsMap);

        final List<ErrorMessage> globalErrors = Lists.newArrayList();
        final ErrorMessage globalErrorMessage = buildErrorMessage(GLOBAL_MESSAGE_1);
        globalErrors.add(globalErrorMessage);
        given(owner.getGlobalErrors()).willReturn(globalErrors);

        // when
        stateChangeContext.setOwner(owner);

        // then
        verify(ownerDD, never()).save(owner);
        verify(stateChangeEntity, never()).setField(describer.getOwnerFieldName(), owner);
        verify(stateChangeEntity, never()).setField(describer.getOwnerFieldName(), savedOwner);
        verify(messageService).addValidationError(stateChangeContext, FIELD_1_NAME, FIELD_1_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(messageService).addValidationError(stateChangeContext, null, GLOBAL_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(stateChangeEntity).setField(describer.getStatusFieldName(), StateChangeStatus.FAILURE.getStringValue());
    }

    @Test
    public final void shouldAddMessagesOnlyOnce() {
        // given
        given(owner.isValid()).willReturn(false);
        given(ownerDD.save(owner)).willReturn(owner);

        final Map<String, ErrorMessage> fieldErrorsMap = Maps.newHashMap();
        final ErrorMessage fieldErrorMessage = buildErrorMessage(FIELD_1_MESSAGE_1);
        fieldErrorsMap.put(FIELD_1_NAME, fieldErrorMessage);
        given(owner.getErrors()).willReturn(fieldErrorsMap);

        final List<ErrorMessage> globalErrors = Lists.newArrayList();
        final ErrorMessage globalErrorMessage = buildErrorMessage(GLOBAL_MESSAGE_1);
        globalErrors.add(globalErrorMessage);
        given(owner.getGlobalErrors()).willReturn(globalErrors);

        // when
        stateChangeContext.setOwner(owner);
        stateChangeContext.setOwner(owner);

        // then
        verify(ownerDD, never()).save(owner);
        verify(stateChangeEntity, never()).setField(describer.getOwnerFieldName(), owner);
        verify(stateChangeEntity, never()).setField(describer.getOwnerFieldName(), savedOwner);
        verify(messageService).addValidationError(stateChangeContext, FIELD_1_NAME, FIELD_1_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(messageService).addValidationError(stateChangeContext, null, GLOBAL_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(stateChangeEntity).setField(describer.getStatusFieldName(), StateChangeStatus.FAILURE.getStringValue());
    }

    @Test
    public final void shouldSaveButNotSetJustInvalidateOwnerEntity() {
        // given
        given(owner.isValid()).willReturn(true);
        given(ownerDD.save(owner)).willReturn(savedOwner);
        given(savedOwner.isValid()).willReturn(false);

        final Map<String, ErrorMessage> fieldErrorsMap = Maps.newHashMap();
        final ErrorMessage fieldErrorMessage = buildErrorMessage(FIELD_1_MESSAGE_1);
        fieldErrorsMap.put(FIELD_1_NAME, fieldErrorMessage);
        given(savedOwner.getErrors()).willReturn(fieldErrorsMap);

        final List<ErrorMessage> globalErrors = Lists.newArrayList();
        final ErrorMessage globalErrorMessage = buildErrorMessage(GLOBAL_MESSAGE_1);
        globalErrors.add(globalErrorMessage);
        given(savedOwner.getGlobalErrors()).willReturn(globalErrors);

        // when
        stateChangeContext.setOwner(owner);

        // then
        verify(ownerDD).save(owner);
        verify(stateChangeEntity, never()).setField(describer.getOwnerFieldName(), owner);
        verify(stateChangeEntity, never()).setField(describer.getOwnerFieldName(), savedOwner);
        verify(messageService).addValidationError(stateChangeContext, FIELD_1_NAME, FIELD_1_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(messageService).addValidationError(stateChangeContext, null, GLOBAL_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(stateChangeEntity).setField(describer.getStatusFieldName(), StateChangeStatus.FAILURE.getStringValue());
    }

    @Test
    public final void shouldCopyValidationErrorMessagesFromEntity() {
        // given
        given(owner.isValid()).willReturn(false);

        final Map<String, ErrorMessage> fieldErrorsMap = Maps.newHashMap();
        final ErrorMessage fieldErrorMessage = buildErrorMessage(FIELD_1_MESSAGE_1);
        fieldErrorsMap.put(FIELD_1_NAME, fieldErrorMessage);
        given(owner.getErrors()).willReturn(fieldErrorsMap);

        final List<ErrorMessage> globalErrors = Lists.newArrayList();
        final ErrorMessage globalErrorMessage = buildErrorMessage(GLOBAL_MESSAGE_1);
        globalErrors.add(globalErrorMessage);
        given(owner.getGlobalErrors()).willReturn(globalErrors);

        // when
        stateChangeContext.setOwner(owner);

        // then
        verify(messageService).addValidationError(stateChangeContext, FIELD_1_NAME, FIELD_1_MESSAGE_1, EMPTY_STRING_ARRAY);
        verify(messageService).addValidationError(stateChangeContext, null, GLOBAL_MESSAGE_1, EMPTY_STRING_ARRAY);
    }

    private ErrorMessage buildErrorMessage(final String message) {
        return new ErrorMessage(message, EMPTY_STRING_ARRAY);
    }
}
