/*
 * 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.skywalking.oap.server.core.alarm.provider;

import org.apache.skywalking.apm.network.event.v3.Event;
import org.apache.skywalking.oap.server.analyzer.event.EventAnalyzerService;
import org.apache.skywalking.oap.server.analyzer.event.EventAnalyzerServiceImpl;
import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
import org.apache.skywalking.oap.server.core.analysis.IDManager;
import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.module.ModuleProviderHolder;
import org.apache.skywalking.oap.server.library.module.ModuleServiceHolder;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class EventHookCallbackTest {

    private ModuleManager moduleManager = mock(ModuleManager.class);

    private ModuleProviderHolder moduleProviderHolder = mock(ModuleProviderHolder.class);

    private ModuleServiceHolder moduleServiceHolder = mock(ModuleServiceHolder.class);

    private MockEventAnalyzerService mockEventAnalyzerService = mock(MockEventAnalyzerService.class);

    private EventAnalyzerService eventAnalyzerService = mock(EventAnalyzerServiceImpl.class);

    @Test
    public void testEventCallbackHasRightFlow() throws Exception {
        List<AlarmMessage> msgs = mockAlarmMessagesHasSingleElement();
        EventHookCallback callback = new EventHookCallback(this.moduleManager);
        when(moduleManager.find("event-analyzer")).thenReturn(moduleProviderHolder);
        when(moduleProviderHolder.provider()).thenReturn(moduleServiceHolder);
        when(moduleServiceHolder.getService(EventAnalyzerService.class)).thenReturn(mockEventAnalyzerService);

        //make sure current service be called.
        callback.doAlarm(msgs);
        verify(mockEventAnalyzerService).analyze(any(Event.class));

        when(moduleServiceHolder.getService(EventAnalyzerService.class)).thenReturn(eventAnalyzerService);
        callback.doAlarm(msgs);
        //Ensure that the current Event is properly constructed
        ArgumentCaptor<Event> argument = ArgumentCaptor.forClass(Event.class);
        verify(eventAnalyzerService).analyze(argument.capture());
        Event value = argument.getValue();
        AlarmMessage msg = msgs.get(0);
        assertEquals(msg.getName(), value.getSource().getService());
        assertEquals("Alarm", value.getName());
        assertEquals(msg.getAlarmMessage(), value.getMessage());
        assertEquals(msg.getPeriod(), (value.getEndTime() - value.getStartTime()) / 1000 / 60);

    }

    @Test
    public void testRelationEventBeProperlyConstructed() {
        List<AlarmMessage> msgs = mockAlarmMessagesHasSourceAndDest();
        EventHookCallback callback = new EventHookCallback(this.moduleManager);
        when(moduleManager.find("event-analyzer")).thenReturn(moduleProviderHolder);
        when(moduleProviderHolder.provider()).thenReturn(moduleServiceHolder);
        when(moduleServiceHolder.getService(EventAnalyzerService.class)).thenReturn(eventAnalyzerService);
        callback.doAlarm(msgs);

        ArgumentCaptor<Event> argument = ArgumentCaptor.forClass(Event.class);
        verify(eventAnalyzerService, times(2)).analyze(argument.capture());
        List<Event> events = argument.getAllValues();
        assertEquals(events.size(), 2);
        Event sourceEvent = events.get(0);
        Event destEvent = events.get(1);
        AlarmMessage msg = msgs.get(0);
        assertEquals(sourceEvent.getSource().getService(), IDManager.ServiceID.analysisId(msg.getId0()).getName());
        assertEquals((sourceEvent.getEndTime() - sourceEvent.getStartTime()) / 1000 / 60, msg.getPeriod());
        assertEquals(destEvent.getSource().getService(), IDManager.ServiceID.analysisId(msg.getId1()).getName());
        assertEquals((destEvent.getEndTime() - destEvent.getStartTime()) / 1000 / 60, msg.getPeriod());
    }

    private List<AlarmMessage> mockAlarmMessagesHasSingleElement() {
        AlarmMessage msg = new AlarmMessage();
        msg.setScopeId(DefaultScopeDefine.SERVICE);
        msg.setScope("SERVICE");
        msg.setName("test-skywalking");
        msg.setId0("dGVzdC1za3l3YWxraW5n.1");
        msg.setAlarmMessage("Alarm caused by Rule service_resp_time_rule");
        msg.setPeriod(3);
        return Arrays.asList(msg);
    }

    private List<AlarmMessage> mockAlarmMessagesHasSourceAndDest() {
        AlarmMessage msg = new AlarmMessage();
        msg.setScopeId(DefaultScopeDefine.SERVICE_RELATION);
        msg.setScope("");
        msg.setName("test-skywalking");
        msg.setId0(IDManager.ServiceID.buildId("sourceIdStr", true));
        msg.setId1(IDManager.ServiceID.buildId("destIdStr", true));
        msg.setAlarmMessage("Alarm caused by Rule service_resp_time_rule");
        msg.setPeriod(5);
        return Arrays.asList(msg);
    }

    class MockEventAnalyzerService implements EventAnalyzerService {

        @Override
        public void analyze(Event event) {
            //ignore current mock process.
        }
    }
}
