/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.causeway.testdomain.interact;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.core.config.presets.CausewayPresets;
import org.apache.causeway.core.metamodel.interactions.managed.ParameterNegotiationModel;
import org.apache.causeway.testdomain.conf.Configuration_headless;
import org.apache.causeway.testdomain.model.interaction.Configuration_usingInteractionDomain;
import org.apache.causeway.testdomain.model.interaction.InteractionDemo;
import org.apache.causeway.testdomain.model.interaction.InteractionDemo_negotiate.Params.NumberRange;
import org.apache.causeway.testdomain.util.interaction.InteractionTestAbstract;

@SpringBootTest(
        classes = {
                Configuration_headless.class,
                Configuration_usingInteractionDomain.class
        },
        properties = {
                "causeway.core.meta-model.introspector.mode=FULL",
                "causeway.applib.annotation.domain-object.editing=TRUE",
                "causeway.core.meta-model.validator.explicit-object-type=FALSE", // does not override any of the imports
        })
@TestPropertySource({
    CausewayPresets.SilenceMetaModel,
    CausewayPresets.SilenceProgrammingModel
})
class ScalarParamNegotiationTest extends InteractionTestAbstract {

    private static enum NegotiationParams {
        RANGE_A,
        A,
        RANGE_B,
        B,
        RANGE_C,
        C;
    }

    ParameterNegotiationModel pendingArgs;

    SimulatedUiChoices uiParamRangeA;
    SimulatedUiChoices uiParamRangeB;
    SimulatedUiChoices uiParamRangeC;

    SimulatedUiChoices uiParamA;
    SimulatedUiChoices uiParamB;
    SimulatedUiAutoComplete uiParamC;

    SimulatedUiSubmit uiSubmit;

    @BeforeEach
    void setUpSimulatedUi() {

        var actionInteraction = startActionInteractionOn(InteractionDemo.class, "negotiate", Where.OBJECT_FORMS)
                .checkVisibility()
                .checkUsability();

        assertTrue(actionInteraction.getManagedAction().isPresent(), "action is expected to be usable");

        var managedAction = actionInteraction.getManagedAction().get();
        pendingArgs = managedAction.startParameterNegotiation();

        // setting up and binding all the simulated UI components

        uiParamRangeA = new SimulatedUiChoices();
        uiParamRangeB = new SimulatedUiChoices();
        uiParamRangeC = new SimulatedUiChoices();

        uiParamA = new SimulatedUiChoices();
        uiParamB = new SimulatedUiChoices();
        uiParamC = new SimulatedUiAutoComplete();

        uiSubmit = new SimulatedUiSubmit();

        uiParamRangeA.bind(pendingArgs, NegotiationParams.RANGE_A.ordinal());
        uiParamRangeB.bind(pendingArgs, NegotiationParams.RANGE_B.ordinal());
        uiParamRangeC.bind(pendingArgs, NegotiationParams.RANGE_C.ordinal());

        uiParamA.bind(pendingArgs, NegotiationParams.A.ordinal());
        uiParamB.bind(pendingArgs, NegotiationParams.B.ordinal());
        uiParamC.bind(pendingArgs, NegotiationParams.C.ordinal());

        uiSubmit.bind(actionInteraction, pendingArgs);

        // verify that initial defaults are as expected

        assertEquals(NumberRange.POSITITVE, uiParamRangeA.getValue().getPojo());
        assertEquals(NumberRange.NEGATIVE, uiParamRangeB.getValue().getPojo());
        assertEquals(NumberRange.ODD, uiParamRangeC.getValue().getPojo());

        assertEquals(1, uiParamA.getValue().getPojo());
        assertEquals(-1, uiParamB.getValue().getPojo());
        assertEquals(-3, uiParamC.getValue().getPojo());

        // verify that initial choices are as expected

        assertComponentWiseUnwrappedEquals(NumberRange.POSITITVE.numbers(), uiParamA.getChoices());
        assertComponentWiseUnwrappedEquals(NumberRange.NEGATIVE.numbers(), uiParamB.getChoices());
        assertEmpty(uiParamC.getChoices()); // empty because the search argument is also empty

        // verify that initial validation messages are all empty,
        // because we don't validate anything until a user initiated submit attempt occurs

        assertEmpty(uiParamRangeA.getValidationMessage());
        assertEmpty(uiParamRangeB.getValidationMessage());
        assertEmpty(uiParamRangeC.getValidationMessage());

        assertEmpty(uiParamA.getValidationMessage());
        assertEmpty(uiParamB.getValidationMessage());
        assertEmpty(uiParamC.getValidationMessage());

        assertEmpty(uiSubmit.getValidationMessage());

        // verify that validation feedback is not active

        assertFalse(pendingArgs.getObservableValidationFeedbackActive().getValue());

    }

    @Test
    void paramC_whenSettingSearchArgument_shouldProvideChoices() {

        // verify that changing the search argument fires change event
        assertDoesIncrement(
                uiParamC::getChoiceBoxUpdateEventCount,
                ()->uiParamC.setSimulatedSearchArgument("-")); // select for all negative and odd numbers

        // verify that no additional changes are triggered
        assertDoesNotIncrement(
                uiParamC::getChoiceBoxUpdateEventCount,
                ()->assertComponentWiseUnwrappedEquals(new int[] {-3, -1}, uiParamC.getChoices()));

        // TODO such a change might set or clear paramC validation message once validation feedback is active
    }

    @Test
    void paramRangeA_whenChanging_shouldUpdateParamAChoices() {

        // verify that changing paramRangeA fires change event
        assertDoesIncrement(
                uiParamRangeA::getSelectedItemUpdateEventCount,
                ()->uiParamRangeA.simulateChoiceSelect(NumberRange.NEGATIVE.ordinal()));

        assertEquals(NumberRange.NEGATIVE, uiParamRangeA.getValue().getPojo());
        assertComponentWiseUnwrappedEquals(NumberRange.NEGATIVE.numbers(), uiParamA.getChoices());

        // TODO such a change might set or clear paramA validation message once validation feedback is active
    }

    @Test
    void whenSimulatedSubmit_shouldActivateValidationFeedback_andPassAfterChangingParam() {

        // failed simulated submit attempt should trigger validation change listeners on the 'action'
        assertDoesIncrement(
                uiSubmit::getValidationUpdateEventCount,
                ()->uiSubmit.simulateSubmit());

        // simulated submit attempt, should have activate validation feedback
        assertTrue(pendingArgs.getObservableValidationFeedbackActive().getValue());

        // unless all validations give green light, submission must be vetoed
        assertEquals(null, uiSubmit.getResult().getSuccess().orElse(null));
        assertEquals("invalid, sum must be zero, got -3", ""+uiSubmit.getResult().getFailureElseFail());

        // verify that changing paramA triggers validation change listeners on the 'action'
        assertDoesIncrement(
                uiSubmit::getValidationUpdateEventCount,
                ()->{

                    // verify that changing paramA does not triggers validation change listeners on paramA,
                    // since paramA is already valid before the change
                    assertDoesNotIncrement(
                            uiParamA::getValidationUpdateEventCount,
                            ()->uiParamA.simulateChoiceSelect(3)); // change parameters, so we pass validation

                });

        // verify that submission is granted now
        uiSubmit.simulateSubmit();

        // verify that we have the expected result returned from the action invocation
        assertTrue(uiSubmit.getResult().isSuccess());
        assertEquals(0, uiSubmit.getResult().getSuccessElseFail().getPojo());

        //TODO exceptions that occur during action invocation could either be rendered
        //     as message, error page or action validation message
    }

    @Test
    void paramRangeA_whenChanging_shouldRenderParamAInvalid() {

        pendingArgs.activateValidationFeedback(); // turn on validation feedback for testing
        assertEquals(null, uiParamA.getValidationMessage()); // expected pre condition

        // verify that changing paramRangeA triggers validation change listeners on paramA
        assertDoesIncrement(
                uiParamA::getValidationUpdateEventCount,
                ()->uiParamRangeA.simulateChoiceSelect(NumberRange.NEGATIVE.ordinal()));

        // not only verify that paramA is invalid, but also that all pending args were considered
        assertEquals(
                "invalid, element not contained in NEGATIVE got 1, param set [1, -1, -3]",
                uiParamA.getValidationMessage());

    }

}
