/*
 * 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.dubbo.config.spring.beans.factory.annotation;

import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.Method;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.config.spring.api.HelloService;
import org.apache.dubbo.config.spring.api.MethodCallback;
import org.apache.dubbo.config.spring.context.annotation.provider.ProviderConfiguration;
import org.apache.dubbo.config.spring.impl.MethodCallbackImpl;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.awaitility.Awaitility.await;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(
        classes = {
            ProviderConfiguration.class,
            MethodConfigCallbackTest.class,
            MethodConfigCallbackTest.MethodCallbackConfiguration.class
        })
@TestPropertySource(properties = {"dubbo.protocol.port=-1", "dubbo.registry.address=${zookeeper.connection.address}"})
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class MethodConfigCallbackTest {

    @BeforeAll
    public static void beforeAll() {
        DubboBootstrap.reset();
    }

    @AfterAll
    public static void afterAll() {
        DubboBootstrap.reset();
    }

    @Autowired
    private ConfigurableApplicationContext context;

    @DubboReference(
            check = false,
            async = true,
            injvm = false, // Currently, local call is not supported method callback cause by Injvm protocol is not
            // supported ClusterFilter
            methods = {
                @Method(
                        name = "sayHello",
                        oninvoke = "methodCallback.oninvoke1",
                        onreturn = "methodCallback.onreturn1",
                        onthrow = "methodCallback.onthrow1")
            })
    private HelloService helloServiceMethodCallBack;

    @DubboReference(
            check = false,
            async = true,
            injvm = false, // Currently, local call is not supported method callback cause by Injvm protocol is not
            // supported ClusterFilter
            methods = {
                @Method(
                        name = "sayHello",
                        oninvoke = "methodCallback.oninvoke2",
                        onreturn = "methodCallback.onreturn2",
                        onthrow = "methodCallback.onthrow2")
            })
    private HelloService helloServiceMethodCallBack2;

    @Test
    void testMethodAnnotationCallBack() {
        int threadCnt = Math.min(4, Runtime.getRuntime().availableProcessors());
        int callCnt = 2 * threadCnt;
        for (int i = 0; i < threadCnt; i++) {
            new Thread(() -> {
                        for (int j = 0; j < callCnt; j++) {
                            helloServiceMethodCallBack.sayHello("dubbo");
                            helloServiceMethodCallBack2.sayHello("dubbo(2)");
                        }
                    })
                    .start();
        }
        await().until(() -> MethodCallbackImpl.cnt.get() >= (2 * threadCnt * callCnt));
        MethodCallback notify = (MethodCallback) context.getBean("methodCallback");
        StringBuilder invoke1Builder = new StringBuilder();
        StringBuilder invoke2Builder = new StringBuilder();
        StringBuilder return1Builder = new StringBuilder();
        StringBuilder return2Builder = new StringBuilder();
        for (int i = 0; i < threadCnt * callCnt; i++) {
            invoke1Builder.append("dubbo invoke success!");
            invoke2Builder.append("dubbo invoke success(2)!");
            return1Builder.append("dubbo return success!");
            return2Builder.append("dubbo return success(2)!");
        }
        Assertions.assertEquals(invoke1Builder.toString(), notify.getOnInvoke1());
        Assertions.assertEquals(return1Builder.toString(), notify.getOnReturn1());
        Assertions.assertEquals(invoke2Builder.toString(), notify.getOnInvoke2());
        Assertions.assertEquals(return2Builder.toString(), notify.getOnReturn2());
    }

    @Configuration
    static class MethodCallbackConfiguration {

        @Bean("methodCallback")
        public MethodCallback methodCallback() {
            return new MethodCallbackImpl();
        }
    }
}
