/*
 * Copyright 2017-present Open Networking Foundation
 *
 * 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.onosproject.openstacknetworking.impl;

import com.google.common.collect.Lists;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.junit.TestUtils;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreServiceAdapter;
import org.onosproject.core.DefaultApplicationId;
import org.onosproject.event.Event;
import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
import org.onosproject.store.service.TestStorageService;
import org.openstack4j.model.network.Router;
import org.openstack4j.openstack.networking.domain.NeutronRouter;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.onosproject.openstacknetworking.api.OpenstackRouterEvent.Type.*;

/**
 * Unit tests for OpenStack router manager.
 */
public class OpenstackRouterManagerTest {

    private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");

    private static final String UNKNOWN_ID = "unknown_id";
    private static final String ROUTER_ID = "router_1";
    private static final String ROUTER_NAME = "router_1";
    private static final Router ROUTER = NeutronRouter.builder()
            .id(ROUTER_ID)
            .name(ROUTER_NAME)
            .build();

    private final TestOpenstackRouterListener testListener = new TestOpenstackRouterListener();

    private OpenstackRouterManager target;
    private DistributedOpenstackRouterStore osRouterStore;

    @Before
    public void setUp() throws Exception {
        osRouterStore = new DistributedOpenstackRouterStore();
        TestUtils.setField(osRouterStore, "coreService", new TestCoreService());
        TestUtils.setField(osRouterStore, "storageService", new TestStorageService());
        TestUtils.setField(osRouterStore, "eventExecutor", MoreExecutors.newDirectExecutorService());
        osRouterStore.activate();

        target = new OpenstackRouterManager();
        target.coreService = new TestCoreService();
        target.osRouterStore = osRouterStore;
        target.addListener(testListener);
        target.activate();
    }

    @After
    public void tearDown() {
        target.removeListener(testListener);
        osRouterStore.deactivate();
        target.deactivate();
        osRouterStore = null;
        target = null;
    }

    /**
     * Tests if getting all routers returns correct set of values.
     */
    @Test
    public void testGetRouters() {
        createBasicRouters();
        assertEquals("Number of router did not match", 1, target.routers().size());
    }

    /**
     * Tests if getting a router with ID returns correct value.
     */
    @Test
    public void testGetRouterById() {
        createBasicRouters();
        assertTrue("Router did not exist", target.router(ROUTER_ID) != null);
        assertTrue("Router did not exist", target.router(UNKNOWN_ID) == null);
    }

    /**
     * Tests creating and removing a router, and checks if proper event is triggered.
     */
    @Test
    public void testCreateAndRemoveRouter() {
        target.createRouter(ROUTER);
        assertEquals("Number of router did not match", 1, target.routers().size());
        assertTrue("Router was not created", target.router(ROUTER_ID) != null);

        target.removeRouter(ROUTER.getId());
        assertEquals("Number of router did not match", 0, target.routers().size());
        assertTrue("Router was not removed", target.router(ROUTER_ID) == null);

        validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_REMOVED);
    }

    /**
     * Tests creating and updating a router, and checks if proper event is triggered.
     */
    @Test
    public void testCreateAndUpdateRouter() {
        target.createRouter(ROUTER);
        assertEquals("Number of router did not match", 1, target.routers().size());
        assertEquals("Router did not match", ROUTER_NAME, target.router(ROUTER_ID).getName());

        final Router updated = NeutronRouter.builder()
                .id(ROUTER_ID)
                .name("updated-name")
                .build();

        target.updateRouter(updated);
        assertEquals("Number of router did not match", 1, target.routers().size());
        assertEquals("Router did not match", "updated-name", target.router(ROUTER_ID).getName());

        validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_UPDATED);
    }

    /**
     * Tests adding and removing external gateway to a router, and checks if
     * proper events are triggered.
     */
    @Test
    public void testAddAndRemoveExternalGateway() {
        target.createRouter(ROUTER);
        assertTrue("Router did not match", target.router(ROUTER_ID).getExternalGatewayInfo() == null);

        Router updated = NeutronRouter.builder()
                .id(ROUTER_ID)
                .name(ROUTER_NAME)
                .externalGateway("test-network-id")
                .build();

        target.updateRouter(updated);
        assertEquals("Router did not match",
                "test-network-id",
                target.router(ROUTER_ID).getExternalGatewayInfo().getNetworkId());

        target.updateRouter(ROUTER);
        assertTrue("Router did not match", target.router(ROUTER_ID).getExternalGatewayInfo() == null);

        validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_UPDATED,
                OPENSTACK_ROUTER_GATEWAY_ADDED,
                OPENSTACK_ROUTER_UPDATED,
                OPENSTACK_ROUTER_GATEWAY_REMOVED);
    }

    /**
     * Tests if creating a router with null value fails with an exception.
     */
    @Test(expected = NullPointerException.class)
    public void testCreateRouterWithNull() {
        target.createRouter(null);
    }

    /**
     * Tests if creating a router with null ID fails with an exception.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testCreateRouterWithNullId() {
        final Router testRouter = NeutronRouter.builder()
                .id(null)
                .name(ROUTER_NAME)
                .build();
        target.createRouter(testRouter);
    }

    /**
     * Tests if creating a duplicate router fails with an exception.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testCreateDuplicateRouter() {
        target.createRouter(ROUTER);
        target.createRouter(ROUTER);
    }

    /**
     * Tests if updating a router with null value fails with an exception.
     */
    @Test(expected = NullPointerException.class)
    public void testUpdateRouterWithNull() {
        target.updateRouter(null);
    }

    /**
     * Tests if updating a router with null ID fails with an exception.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testUpdateRouterWithNullId() {
        final Router testRouter = NeutronRouter.builder()
                .id(null)
                .name(ROUTER_NAME)
                .build();
        target.updateRouter(testRouter);
    }

    /**
     * Tests if updating a router with null name fails with an exception.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testUpdateRouterWithNullName() {
        final Router testRouter = NeutronRouter.builder()
                .id(ROUTER_ID)
                .name(null)
                .build();
        target.updateRouter(testRouter);
    }

    /**
     * Tests if updating an unregistered router fails with an exception.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testUpdateUnregisteredRouter() {
        target.updateRouter(ROUTER);
    }

    // TODO fix openstack4j floating IP data model and add unit tests

    private void createBasicRouters() {
        target.createRouter(ROUTER);
    }

    private static class TestCoreService extends CoreServiceAdapter {

        @Override
        public ApplicationId registerApplication(String name) {
            return TEST_APP_ID;
        }
    }

    private static class TestOpenstackRouterListener implements OpenstackRouterListener {
        private List<OpenstackRouterEvent> events = Lists.newArrayList();

        @Override
        public void event(OpenstackRouterEvent event) {
            events.add(event);
        }
    }

    private void validateEvents(Enum... types) {
        int i = 0;
        assertEquals("Number of events did not match", types.length, testListener.events.size());
        for (Event event : testListener.events) {
            assertEquals("Incorrect event received", types[i], event.type());
            i++;
        }
        testListener.events.clear();
    }
}
