package com.alibaba.csp.sentinel.adapter.reactor;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;

import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.node.EntranceNode;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;

import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import static org.junit.Assert.*;

/**
 * @author Eric Zhao
 */
public class MonoSentinelOperatorIntegrationTest {

    @Test
    public void testTransformMonoWithSentinelContextEnter() {
        String resourceName = createResourceName("testTransformMonoWithSentinelContextEnter");
        String contextName = "test_reactive_context";
        String origin = "originA";
        FlowRuleManager.loadRules(Collections.singletonList(
            new FlowRule(resourceName).setCount(0).setLimitApp(origin).as(FlowRule.class)
        ));
        StepVerifier.create(Mono.just(2)
            .transform(new SentinelReactorTransformer<>(
                // Customized context with origin.
                new EntryConfig(resourceName, EntryType.OUT, new ContextConfig(contextName, origin))))
        )
            .expectError(BlockException.class)
            .verify();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(0, cn.passQps(), 0.01);
        assertEquals(1, cn.blockRequest());
        assertTrue(Constants.ROOT.getChildList()
            .stream()
            .filter(node -> node instanceof EntranceNode)
            .map(e -> (EntranceNode)e)
            .anyMatch(e -> e.getId().getName().equals(contextName))
        );

        FlowRuleManager.loadRules(new ArrayList<>());
    }

    @Test
    public void testFluxToMonoNextThenCancelSuccess() {
        String resourceName = createResourceName("testFluxToMonoNextThenCancelSuccess");
        StepVerifier.create(Flux.range(1, 10)
            .map(e -> e * 2)
            .next()
            .transform(new SentinelReactorTransformer<>(resourceName)))
            .expectNext(2)
            .verifyComplete();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(1, cn.passQps(), 0.01);
    }

    @Test
    public void testEmitSingleLongTimeRt() {
        String resourceName = createResourceName("testEmitSingleLongTimeRt");
        StepVerifier.create(Mono.just(2)
            .delayElement(Duration.ofMillis(1000))
            .map(e -> e * 2)
            .transform(new SentinelReactorTransformer<>(resourceName)))
            .expectNext(4)
            .verifyComplete();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(1000, cn.avgRt(), 20);
    }

    @Test
    public void testEmitEmptySuccess() {
        String resourceName = createResourceName("testEmitEmptySuccess");
        StepVerifier.create(Mono.empty()
            .transform(new SentinelReactorTransformer<>(resourceName)))
            .verifyComplete();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(1, cn.passQps(), 0.01);
    }

    @Test
    public void testEmitSingleSuccess() {
        String resourceName = createResourceName("testEmitSingleSuccess");
        StepVerifier.create(Mono.just(1)
            .transform(new SentinelReactorTransformer<>(resourceName)))
            .expectNext(1)
            .verifyComplete();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(1, cn.passQps(), 0.01);
    }

    @Test
    public void testEmitSingleValueWhenFlowControlTriggered() {
        String resourceName = createResourceName("testEmitSingleValueWhenFlowControlTriggered");
        FlowRuleManager.loadRules(Collections.singletonList(
            new FlowRule(resourceName).setCount(0)
        ));
        StepVerifier.create(Mono.just(1)
            .map(e -> e * 2)
            .transform(new SentinelReactorTransformer<>(resourceName)))
            .expectError(BlockException.class)
            .verify();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(0, cn.passQps(), 0.01);
        assertEquals(1, cn.blockRequest());

        FlowRuleManager.loadRules(new ArrayList<>());
    }

    @Test
    public void testEmitExceptionWhenFlowControlTriggered() {
        String resourceName = createResourceName("testEmitExceptionWhenFlowControlTriggered");
        FlowRuleManager.loadRules(Collections.singletonList(
            new FlowRule(resourceName).setCount(0)
        ));
        StepVerifier.create(Mono.error(new IllegalStateException("some"))
            .transform(new SentinelReactorTransformer<>(resourceName)))
            .expectError(BlockException.class)
            .verify();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(0, cn.passQps(), 0.01);
        assertEquals(1, cn.blockRequest());

        FlowRuleManager.loadRules(new ArrayList<>());
    }

    @Test
    public void testEmitSingleError() {
        String resourceName = createResourceName("testEmitSingleError");
        StepVerifier.create(Mono.error(new IllegalStateException())
            .transform(new SentinelReactorTransformer<>(resourceName)))
            .expectError(IllegalStateException.class)
            .verify();

        ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName);
        assertNotNull(cn);
        assertEquals(1, cn.totalException());
    }

    @Test
    public void testMultipleReactorTransformerLatterFlowControl() {
        String resourceName1 = createResourceName("testMultipleReactorTransformerLatterFlowControl1");
        String resourceName2 = createResourceName("testMultipleReactorTransformerLatterFlowControl2");
        FlowRuleManager.loadRules(Collections.singletonList(
            new FlowRule(resourceName2).setCount(0)
        ));
        StepVerifier.create(Mono.just(2)
            .transform(new SentinelReactorTransformer<>(resourceName1))
            .transform(new SentinelReactorTransformer<>(resourceName2)))
            .expectError(BlockException.class)
            .verify();

        ClusterNode cn1 = ClusterBuilderSlot.getClusterNode(resourceName1);
        assertNotNull(cn1);
        ClusterNode cn2 = ClusterBuilderSlot.getClusterNode(resourceName2);
        assertNotNull(cn2);
        assertEquals(1, cn2.blockRequest());
        assertEquals(1, cn1.totalSuccess());
        FlowRuleManager.loadRules(new ArrayList<>());
    }

    private String createResourceName(String resourceName) {
        return "reactor_test_mono_" + resourceName;
    }
}