/* Copyright (c) 2010-2018. Axon Framework
 *
 * Licensed 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.axonframework.integrationtests.commandhandling;

import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.deadline.DeadlineManager;
import org.axonframework.eventsourcing.EventSourcingHandler;
import org.axonframework.modelling.command.*;
import org.axonframework.spring.stereotype.Aggregate;
import org.axonframework.test.aggregate.AggregateTestFixture;
import org.axonframework.test.aggregate.FixtureConfiguration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Test whether aggregate member annotated collections of the same generic type be able to handle command.
 *
 * @author Somrak Monpengpinij
 */
public class AbstractAggregateMemberTest {
    private FixtureConfiguration<FactoryAggregate> fixture;
    private String factoryId = "factoryId";

    @BeforeEach
    public void setUp(){
        fixture = new AggregateTestFixture(FactoryAggregate.class);
    }

    @Test
    public void testInitFactoryAggregate_ShouldHandleCommandNormally(){
        fixture.givenNoPriorActivity()
                .when(new CreateFactoryCommand(factoryId))
                .expectEvents(new FactoryCreatedEvent(factoryId));
    }

    @Test
    public void testEmployeesAggregateMember_ShouldBeAbleToHandleCommand(){
        fixture.givenCommands(new CreateFactoryCommand(factoryId))
                .when(new CreateTaskCommand(factoryId, "employeeId"))
                .expectEvents(new EmployeeTaskCreatedEvent(factoryId, "employeeId"));
    }

    @Test
    public void testManagersAggregateMember_ShouldBeAbleToHandleCommand(){
        fixture.givenCommands(new CreateFactoryCommand(factoryId))
                .when(new CreateTaskCommand(factoryId, "managerId"))
                .expectEvents(new ManagerTaskCreatedEvent(factoryId, "managerId"));
    }

    @Test
    public void testSendCommandToNoneExistEntity_ShouldThrowAggregateEntityNotFoundException(){
        fixture.givenCommands(new CreateFactoryCommand(factoryId))
                .when(new CreateTaskCommand(factoryId, "none-exist-id"))
                .expectException(AggregateEntityNotFoundException.class);
    }

    private static abstract class Person {
        @EntityId
        public String personId;
    }

    private static class Employee extends Person {

        public Employee(){
        }

        public Employee(String personId){
            this.personId = personId;
        }

        @CommandHandler
        public void handle(CreateTaskCommand cmd){
            AggregateLifecycle.apply(new EmployeeTaskCreatedEvent(
                    cmd.getFactoryId(),
                    cmd.getPersonId()
            ));
        }

        @EventSourcingHandler
        public void on(EmployeeTaskCreatedEvent event){
        }

        public String getPersonId(){
            return personId;
        }

        @Override
        public int hashCode(){
            return this.personId.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Employee){
                Employee manager = (Employee) o;
                return manager.personId == this.personId;
            }
            return false;
        }
    }

    private static class Manager extends Person {

        public Manager(){
        }

        public Manager(String personId){
            this.personId = personId;
        }

        @CommandHandler
        public void handle(CreateTaskCommand cmd){
            AggregateLifecycle.apply(new ManagerTaskCreatedEvent(
                    cmd.getFactoryId(),
                    cmd.getPersonId()
            ));
        }

        @EventSourcingHandler
        public void on(ManagerTaskCreatedEvent event){
        }

        public String getPersonId(){
            return personId;
        }

        @Override
        public int hashCode(){
            return this.personId.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Manager){
                Manager manager = (Manager) o;
                return manager.personId == this.personId;
            }
            return false;
        }
    }

    @Aggregate
    private static class FactoryAggregate {
        @AggregateIdentifier
        public String factoryId;

        public List<Person> persons = new ArrayList<>();

        @AggregateMember(type = Employee.class)
        public List<Person> employees(){
            return persons.stream()
                          .filter(p -> p instanceof Employee)
                          .collect(Collectors.toList());
        }

        @AggregateMember(type = Manager.class)
        public List<Person> managers(){
            return persons.stream()
                          .filter(p -> p instanceof Manager)
                          .collect(Collectors.toList());
        }

        public String getFactoryId(){
            return factoryId;
        }

        public FactoryAggregate(){

        }

        @CommandHandler
        public FactoryAggregate(CreateFactoryCommand cmd){
            AggregateLifecycle.apply(new FactoryCreatedEvent(
                    cmd.getFactoryId()
            ));
        }

        @EventSourcingHandler
        public void on(FactoryCreatedEvent event){
            this.factoryId = event.getFactoryId();
            this.persons.add(new Employee(
                    "employeeId"
            ));
            this.persons.add(new Manager(
                    "managerId"
            ));
        }

        @Override
        public int hashCode(){
            return this.factoryId.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof FactoryAggregate){
                FactoryAggregate manager = (FactoryAggregate) o;
                return manager.factoryId == this.factoryId;
            }
            return false;
        }
    }

    private static class CreateTaskCommand {
        @TargetAggregateIdentifier
        public String factoryId;
        public String personId;

        public CreateTaskCommand(){}

        public CreateTaskCommand(String factoryId, String personId){
            this.factoryId = factoryId;
            this.personId = personId;
        }

        public String getFactoryId(){
            return factoryId;
        }

        public String getPersonId(){
            return personId;
        }
    }

    private static class EmployeeTaskCreatedEvent {
        String factoryId;
        String personId;
        public EmployeeTaskCreatedEvent(String factoryId, String personId){
            this.factoryId = factoryId;
            this.personId = personId;
        }
    }

    private static class ManagerTaskCreatedEvent {
        String factoryId;
        String personId;
        public ManagerTaskCreatedEvent(String factoryId, String personId){
            this.factoryId = factoryId;
            this.personId = personId;
        }
    }

    private static class CreateFactoryCommand {
        @TargetAggregateIdentifier
        public String factoryId;
        public CreateFactoryCommand(String factoryId){
            this.factoryId = factoryId;
        }

        public String getFactoryId(){
            return factoryId;
        }
    }

    private static class FactoryCreatedEvent {
        public String factoryId;
        public FactoryCreatedEvent(String factoryId){
            this.factoryId = factoryId;
        }

        public String getFactoryId(){
            return factoryId;
        }
    }
}

@@ -1312,22 +1312,23 @@
 able to 
-handle
+forward
  command
@@ -1665,16 +1665,24 @@
 testInit
+ializing
 FactoryA
@@ -1706,23 +1706,26 @@
 ndle
-CommandNormally
+BeAbleToInitialize
 ()%7B%0A
@@ -1930,60 +1930,104 @@
 test
-EmployeesAggregateMember_ShouldBeAbleToHandleCommand
+ForwardingCommandToAggregateMemberWithTheSameGenericType_ShouldForwardCommandToEmployeeAggregate
 ()%7B%0A
@@ -2290,59 +2290,103 @@
 test
-ManagersAggregateMember_ShouldBeAbleToHandleCommand
+ForwardingCommandToAggregateMemberWithTheSameGenericType_ShouldForwardCommandToManagerAggregate
 ()%7B%0A

refactor more meaningful test name