/*
 * 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.camel.k.main;

import java.util.List;

import org.apache.camel.CamelContext;
import org.apache.camel.Route;
import org.apache.camel.component.knative.KnativeComponent;
import org.apache.camel.component.knative.spi.Knative;
import org.apache.camel.component.knative.spi.KnativeEnvironment;
import org.apache.camel.component.knative.test.KnativeEnvironmentSupport;
import org.apache.camel.k.Runtime;
import org.apache.camel.k.http.PlatformHttpServiceContextCustomizer;
import org.apache.camel.k.listener.ContextConfigurer;
import org.apache.camel.k.listener.SourcesConfigurer;
import org.apache.camel.k.main.support.MyBean;
import org.apache.camel.k.main.support.MyProcessor;
import org.apache.camel.k.support.SourcesSupport;
import org.apache.camel.k.test.AvailablePortFinder;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.ToDefinition;
import org.apache.camel.util.ObjectHelper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.apache.camel.util.CollectionHelper.mapOf;
import static org.assertj.core.api.Assertions.assertThat;

public class RuntimeTest {
    private ApplicationRuntime runtime;

    @BeforeEach
    public void setUp() {
        runtime = new ApplicationRuntime();
    }

    @AfterEach
    public void cleanUp() throws Exception {
        if (runtime != null) {
            runtime.stop();
        }
    }

    @Test
    void testLoadMultipleRoutes() throws Exception {
        runtime.addListener(new ContextConfigurer());
        runtime.addListener(SourcesSupport.forRoutes("classpath:r1.js", "classpath:r2.mytype?language=js"));
        runtime.addListener(Runtime.Phase.Started, r -> {
            CamelContext context = r.getCamelContext();
            List<Route> routes = context.getRoutes();

            assertThat(routes).hasSize(2);
            assertThat(routes).anyMatch(p -> ObjectHelper.equal("r1", p.getId()));
            assertThat(routes).anyMatch(p -> ObjectHelper.equal("r2", p.getId()));

            runtime.stop();
        });

        runtime.run();
    }

    @Test
    void testLoadRouteAndRest() throws Exception {
        runtime.addListener(new ContextConfigurer());
        runtime.addListener(SourcesSupport.forRoutes("classpath:routes.xml", "classpath:rests.xml"));
        runtime.addListener(Runtime.Phase.Started, r -> {
            ModelCamelContext context = r.getCamelContext(ModelCamelContext.class);

            assertThat(context.getRouteDefinitions()).isNotEmpty();
            assertThat(context.getRestDefinitions()).isNotEmpty();

            runtime.stop();
        });

        runtime.run();
    }

    @Test
    void testLoadRouteWithExpression() throws Exception {
        runtime.setProperties(mapOf(
            "the.body", "10"
        ));

        runtime.addListener(new ContextConfigurer());
        runtime.addListener(SourcesSupport.forRoutes("classpath:routes-with-expression.xml"));
        runtime.addListener(Runtime.Phase.Started, Runtime::stop);
        runtime.run();
    }

    @Test
    public void testLoadJavaSource() throws Exception {
        runtime.setProperties(mapOf(
            "camel.beans.myProcessor", "#class:" + MyProcessor.class.getName()
        ));

        runtime.addListener(SourcesSupport.forRoutes("classpath:MyRoutesWithBeans.java"));
        runtime.addListener(Runtime.Phase.Started, r -> {
            assertThat(runtime.getCamelContext().getRoutes()).hasSize(1);
            assertThat(runtime.getRegistry().lookupByName("myProcessor")).isNotNull();
            assertThat(runtime.getRegistry().lookupByName("my-bean")).isInstanceOfSatisfying(MyBean.class, b -> {
                assertThat(b).hasFieldOrPropertyWithValue("name", "my-bean-name");
            });
            r.stop();
        });
        runtime.run();
    }

    @Test
    public void testLoadJavaSourceFromProperties() throws Exception {
        runtime.setInitialProperties(
            "camel.k.sources[0].name", "MyRoutesWithBeans",
            "camel.k.sources[0].location", "classpath:MyRoutesWithBeans.java",
            "camel.k.sources[0].language", "java"
        );
        runtime.setProperties(mapOf(
            "camel.beans.myProcessor", "#class:" + MyProcessor.class.getName()
        ));

        runtime.addListener(new SourcesConfigurer());
        runtime.addListener(Runtime.Phase.Started, r -> {
            assertThat(runtime.getCamelContext().getRoutes()).hasSize(1);
            assertThat(runtime.getRegistry().lookupByName("myProcessor")).isNotNull();
            assertThat(runtime.getRegistry().lookupByName("my-bean")).isInstanceOfSatisfying(MyBean.class, b -> {
                assertThat(b).hasFieldOrPropertyWithValue("name", "my-bean-name");
            });
            r.stop();
        });
        runtime.run();
    }

    @Test
    public void testLoadJavaSourceFromSimpleProperties() throws Exception {
        runtime.setInitialProperties(
            "camel.k.sources[0].location", "classpath:MyRoutesWithBeans.java"
        );
        runtime.setProperties(mapOf(
            "camel.beans.myProcessor", "#class:" + MyProcessor.class.getName()
        ));

        runtime.addListener(new SourcesConfigurer());
        runtime.addListener(Runtime.Phase.Started, Runtime::stop);
        runtime.run();

        assertThat(runtime.getRegistry().lookupByName("myProcessor")).isNotNull();
        assertThat(runtime.getRegistry().lookupByName("my-bean")).isInstanceOfSatisfying(MyBean.class, b -> {
            assertThat(b).hasFieldOrPropertyWithValue("name", "my-bean-name");
        });
    }

    @Test
    public void testLoadJavaSourceWrap() throws Exception {
        KnativeComponent component = new KnativeComponent();
        component.setEnvironment(KnativeEnvironment.on(
            KnativeEnvironmentSupport.endpoint(Knative.EndpointKind.sink, "sink", "http://localhost:" + AvailablePortFinder.getNextAvailable())
        ));

        PlatformHttpServiceContextCustomizer phsc = new PlatformHttpServiceContextCustomizer();
        phsc.setBindPort(AvailablePortFinder.getNextAvailable());
        phsc.apply(runtime.getCamelContext());

        runtime.getCamelContext().addComponent("knative", component);
        runtime.addListener(SourcesSupport.forRoutes("classpath:MyRoutesWithBeans.java?interceptors=knative-source"));
        runtime.addListener(Runtime.Phase.Started, Runtime::stop);
        runtime.run();

        assertThat(runtime.getRegistry().lookupByName("my-bean")).isInstanceOfSatisfying(MyBean.class, b -> {
            assertThat(b).hasFieldOrPropertyWithValue("name", "my-bean-name");
        });
        assertThat(runtime.getCamelContext(ModelCamelContext.class).getRouteDefinition("my-route")).satisfies(definition -> {
            assertThat(definition.getOutputs()).last().isInstanceOfSatisfying(ToDefinition.class, to -> {
                assertThat(to.getEndpointUri()).isEqualTo("knative://endpoint/sink");
            });
        });
    }

}
