/*
 * 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.nifi.reporting.prometheus;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.nifi.controller.status.PortStatus;
import org.apache.nifi.controller.status.ProcessGroupStatus;
import org.apache.nifi.controller.status.RunStatus;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.prometheus.util.PrometheusMetricsUtil;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.state.MockStateManager;
import org.apache.nifi.util.MockComponentLog;
import org.apache.nifi.util.MockConfigurationContext;
import org.apache.nifi.util.MockReportingContext;
import org.apache.nifi.util.MockReportingInitializationContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public class PrometheusReportingTaskIT {
    private static final String TEST_INIT_CONTEXT_ID = "test-init-context-id";
    private static final String TEST_INIT_CONTEXT_NAME = "test-init-context-name";
    private static final String TEST_TASK_ID = "test-task-id";
    private MockReportingInitializationContext reportingInitContextStub;
    private MockReportingContext reportingContextStub;
    private MockConfigurationContext configurationContextStub; // new
    private PrometheusReportingTask testedReportingTask;
    private ProcessGroupStatus rootGroupStatus;

    @BeforeEach
    public void setup() {
        testedReportingTask = new PrometheusReportingTask();
        rootGroupStatus = new ProcessGroupStatus();
        reportingInitContextStub = new MockReportingInitializationContext(TEST_INIT_CONTEXT_ID, TEST_INIT_CONTEXT_NAME,
                new MockComponentLog(TEST_TASK_ID, testedReportingTask));

        reportingContextStub = new MockReportingContext(Collections.emptyMap(), new MockStateManager(testedReportingTask));

        reportingContextStub.setProperty(PrometheusMetricsUtil.INSTANCE_ID.getName(), "localhost");

        configurationContextStub = new MockConfigurationContext(reportingContextStub.getProperties(),
                reportingContextStub.getControllerServiceLookup(), null);

        rootGroupStatus.setId("1234");
        rootGroupStatus.setFlowFilesReceived(5);
        rootGroupStatus.setBytesReceived(10000);
        rootGroupStatus.setFlowFilesSent(10);
        rootGroupStatus.setBytesSent(20000);
        rootGroupStatus.setQueuedCount(100);
        rootGroupStatus.setQueuedContentSize(1024L);
        rootGroupStatus.setBytesRead(60000L);
        rootGroupStatus.setBytesWritten(80000L);
        rootGroupStatus.setActiveThreadCount(5);
        rootGroupStatus.setName("root");
        rootGroupStatus.setFlowFilesTransferred(5);
        rootGroupStatus.setBytesTransferred(10000);
        rootGroupStatus.setOutputContentSize(1000L);
        rootGroupStatus.setInputContentSize(1000L);
        rootGroupStatus.setOutputCount(100);
        rootGroupStatus.setInputCount(1000);

        PortStatus outputPortStatus = new PortStatus();
        outputPortStatus.setId("9876");
        outputPortStatus.setName("out");
        outputPortStatus.setGroupId("1234");
        outputPortStatus.setRunStatus(RunStatus.Stopped);
        outputPortStatus.setActiveThreadCount(1);

        rootGroupStatus.setOutputPortStatus(Collections.singletonList(outputPortStatus));
        // Create a nested group status
        ProcessGroupStatus groupStatus2 = new ProcessGroupStatus();
        groupStatus2.setFlowFilesReceived(5);
        groupStatus2.setBytesReceived(10000);
        groupStatus2.setFlowFilesSent(10);
        groupStatus2.setBytesSent(20000);
        groupStatus2.setQueuedCount(100);
        groupStatus2.setQueuedContentSize(1024L);
        groupStatus2.setActiveThreadCount(2);
        groupStatus2.setBytesRead(12345L);
        groupStatus2.setBytesWritten(11111L);
        groupStatus2.setFlowFilesTransferred(5);
        groupStatus2.setBytesTransferred(10000);
        groupStatus2.setOutputContentSize(1000L);
        groupStatus2.setInputContentSize(1000L);
        groupStatus2.setOutputCount(100);
        groupStatus2.setInputCount(1000);
        groupStatus2.setId("3378");
        groupStatus2.setName("nestedPG");
        Collection<ProcessGroupStatus> nestedGroupStatuses = new ArrayList<>();
        nestedGroupStatuses.add(groupStatus2);
        rootGroupStatus.setProcessGroupStatus(nestedGroupStatuses);
    }

    @Test
    public void testOnTrigger() throws IOException, InitializationException {
        testedReportingTask.initialize(reportingInitContextStub);
        testedReportingTask.onScheduled(configurationContextStub);
        reportingContextStub.getEventAccess().setProcessGroupStatus(rootGroupStatus);
        testedReportingTask.onTrigger(reportingContextStub);

        String content = getMetrics();
        assertTrue(content.contains(
                "nifi_amount_flowfiles_received{instance=\"localhost\",component_type=\"RootProcessGroup\",component_name=\"root\",component_id=\"1234\",parent_id=\"\",} 5.0"));
        assertTrue(content.contains(
                "nifi_amount_threads_active{instance=\"localhost\",component_type=\"RootProcessGroup\",component_name=\"root\",component_id=\"1234\",parent_id=\"\",} 5.0"));
        assertTrue(content.contains(
                "nifi_amount_threads_active{instance=\"localhost\",component_type=\"ProcessGroup\",component_name=\"nestedPG\",component_id=\"3378\",parent_id=\"1234\",} 2.0"));

        // Rename the component
        rootGroupStatus.setName("rootroot");
        content = getMetrics();
        assertFalse(content.contains(
                "nifi_amount_flowfiles_received{instance=\"localhost\",component_type=\"RootProcessGroup\",component_name=\"root\",component_id=\"1234\",parent_id=\"\",} 5.0"));
        assertFalse(content.contains(
                "nifi_amount_threads_active{instance=\"localhost\",component_type=\"RootProcessGroup\",component_name=\"root\",component_id=\"1234\",parent_id=\"\",} 5.0"));
        assertTrue(content.contains(
                "nifi_amount_flowfiles_received{instance=\"localhost\",component_type=\"RootProcessGroup\",component_name=\"rootroot\",component_id=\"1234\",parent_id=\"\",} 5.0"));
        assertTrue(content.contains(
                "nifi_amount_threads_active{instance=\"localhost\",component_type=\"RootProcessGroup\",component_name=\"rootroot\",component_id=\"1234\",parent_id=\"\",} 5.0"));
        try {
            testedReportingTask.OnStopped();
        } catch (Exception e) {
            // Ignore
        }
    }

    private String getMetrics() throws IOException {
        HttpURLConnection con = (HttpURLConnection) URI.create("http://localhost:9092/metrics").toURL().openConnection();
        con.setRequestMethod("GET");
        int status = con.getResponseCode();
        assertEquals(HttpURLConnection.HTTP_OK, status);

        HttpClient client = HttpClientBuilder.create().build();
        HttpGet request = new HttpGet("http://localhost:9092/metrics");
        HttpResponse response = client.execute(request);
        HttpEntity entity = response.getEntity();
        return EntityUtils.toString(entity);
    }

    @Test
    public void testNullLabel() throws IOException, InitializationException {
        rootGroupStatus.setName(null);
        testedReportingTask.initialize(reportingInitContextStub);
        testedReportingTask.onScheduled(configurationContextStub);
        reportingContextStub.getEventAccess().setProcessGroupStatus(rootGroupStatus);
        testedReportingTask.onTrigger(reportingContextStub);

        String content = getMetrics();
        assertTrue(content.contains("parent_id=\"\""));

        try {
            testedReportingTask.OnStopped();
        } catch(Exception e) {
            // Ignore
        }
    }

    @Test
    public void testTwoInstances() throws InitializationException {
        testedReportingTask.initialize(reportingInitContextStub);
        testedReportingTask.onScheduled(configurationContextStub);
        PrometheusReportingTask testedReportingTask2 = new PrometheusReportingTask();
        testedReportingTask2.initialize(reportingInitContextStub);
        try {
            testedReportingTask2.onScheduled(configurationContextStub);
            fail("Should have reported Address In Use");
        } catch (ProcessException pe) {
            // Do nothing, this is the expected behavior
        }
    }
}
