/*
 * Licensed to The OpenNMS Group, Inc (TOG) under one or more
 * contributor license agreements.  See the LICENSE.md file
 * distributed with this work for additional information
 * regarding copyright ownership.
 *
 * TOG licenses this file to You under the GNU Affero General
 * Public License Version 3 (the "License") or (at your option)
 * any later version.  You may not use this file except in
 * compliance with the License.  You may obtain a copy of the
 * License at:
 *
 *      https://www.gnu.org/licenses/agpl-3.0.txt
 *
 * 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.opennms.netmgt.eventd;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import java.math.BigInteger;
import java.util.Date;
import java.util.UUID;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.opennms.core.test.OpenNMSJUnit4ClassRunner;
import org.opennms.core.test.db.annotations.JUnitTemporaryDatabase;
import org.opennms.core.utils.Base64;
import org.opennms.netmgt.eventd.processor.expandable.ExpandableParameter;
import org.opennms.netmgt.events.api.EventConstants;
import org.opennms.netmgt.mock.MockEventUtil;
import org.opennms.netmgt.mock.MockNetwork;
import org.opennms.netmgt.mock.MockService;
import org.opennms.netmgt.model.OnmsSeverity;
import org.opennms.netmgt.model.events.EventBuilder;
import org.opennms.netmgt.xml.event.Event;
import org.opennms.netmgt.xml.event.Tticket;
import org.opennms.netmgt.xml.event.Value;
import org.opennms.test.JUnitConfigurationEnvironment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;

import com.google.common.collect.Maps;

@RunWith(OpenNMSJUnit4ClassRunner.class)
@ContextConfiguration(locations={
        "classpath:/META-INF/opennms/applicationContext-soa.xml",
        "classpath:/META-INF/opennms/applicationContext-dao.xml",
        "classpath:/META-INF/opennms/applicationContext-mockConfigManager.xml",
        "classpath*:/META-INF/opennms/component-dao.xml",
        "classpath:/META-INF/opennms/applicationContext-commonConfigs.xml",
        "classpath:/META-INF/opennms/applicationContext-mockSnmpPeerFactory.xml",
        "classpath:/META-INF/opennms/applicationContext-minimal-conf.xml",
})
@JUnitConfigurationEnvironment
@JUnitTemporaryDatabase(dirtiesContext=false)
public class EventUtilIT {

    private final MockNetwork m_network = new MockNetwork();
    private MockService m_svc;
    private Event m_svcLostEvent;
    private Event m_nodeDownEvent;
    private Event m_bgpBkTnEvent;

    @Autowired
    EventUtil eventUtil;
    
    @Before
    public void setUp() throws Exception {
        m_network.createStandardNetwork();
        m_svc = m_network.getService(1, "192.168.1.1", "SMTP");
        m_svcLostEvent = MockEventUtil.createNodeLostServiceEvent("Test", m_svc);
        m_nodeDownEvent = MockEventUtil.createNodeDownEvent("Text", m_network.getNode(1));
        m_bgpBkTnEvent = MockEventUtil.createBgpBkTnEvent("Test", m_network.getNode(1), "128.64.32.16", 2);
    }

    @After
    public void tearDown() throws Exception {
    }

    /*
     * Test method for 'org.opennms.netmgt.eventd.EventUtil.getValueAsString(Value)'
     */
    @Test
    public void testGetValueAsString() {
        Value v = new Value();
        v.setContent(String.valueOf(Base64.encodeBase64((new String("abcd")).getBytes())));
        v.setEncoding("base64");
        
        assertEquals("0x61626364", EventConstants.getValueAsString(v));
    }

    /*
     * Test method for 'org.opennms.netmgt.eventd.EventUtil.escape(String, char)'
     */
    @Test
    public void testEscape() {
        assertEquals("m%onkeys%47rock", AbstractEventUtil.escape("m%onkeys/rock", '/'));
    }

    @Test
    public void testGetValueOfParm() {
        String testString = new ExpandableParameter(AbstractEventUtil.TAG_UEI, eventUtil).expand(m_svcLostEvent, Maps.newHashMap());
        assertEquals(EventConstants.NODE_LOST_SERVICE_EVENT_UEI, testString);
        
        m_svcLostEvent.setSeverity(OnmsSeverity.MINOR.getLabel());
        testString = new ExpandableParameter(AbstractEventUtil.TAG_SEVERITY, eventUtil).expand(m_svcLostEvent, Maps.newHashMap());
        assertEquals("Minor", testString);

        Event event = MockEventUtil.createNodeLostServiceEvent("Test", m_svc, "noReasonAtAll");
        assertEquals("noReasonAtAll", eventUtil.getNamedParmValue("parm["+EventConstants.PARM_LOSTSERVICE_REASON+"]", event));
    }

    /*
     * Test method for 'org.opennms.netmgt.eventd.EventUtil.expandParms(String, Event)'
     */
    @Test
    public void testExpandParms() {
        String testString = "%uei%:%dpname%:%nodeid%:%interface%:%service%";
        
        String newString = eventUtil.expandParms(testString, m_svcLostEvent);
        assertEquals(EventConstants.NODE_LOST_SERVICE_EVENT_UEI + "::1:192.168.1.1:SMTP", newString);
    }
    
    /**
     * Test method for ignoring things that aren't params because of whitespace across lines.
     */
    @Test
    public void testLineWhitespaceParms(){
        String testString = "%uei%:"+
                " #description#\n"+
                "<p>The interface 172.17.12.251 generated a Syslog Message.<br>\n"+
                " Node ID: 0<br>\n"+
                " Host: Unknown<br>\n"+
                " Interface: 172.17.12.251 <br>\n"+
                " Message: 172.17.12.251: Mar 5 20:48:35.644: %SSH-4-SSH2_UNEXPECTED_MSG: Unexpected message type has arrived. Terminating the connection <br>\n"+
                " Process: 304806 <br>\n"+
                " PID: \n"+
                " </p>\n"+
                "#/description#\n"+
                "41\n"+
                "Unknown\n"+
                "172.17.12.251\n"+
                "Warning\n"+
                "Thursday, March 5, 2015 2:48:47 PM CST\n"+
                ".\n"+
                "uei.opennms.org/syslogd/local7/Warning\n"+
                "syslogmessage=\"172.17.12.251: Mar 5 20:48:35.644: %SSH-4-SSH2_UNEXPECTED_MSG: Unexpected message type has arrived. Terminating the connection\" severity=\"Warning\" timestamp=\"Mar 05 14:48:47\" process=\"304806\" service=\"local7\""
                + ":%dpname%:%nodeid%";
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        String validString = "http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition:" +
                " #description#\n"+
                "<p>The interface 172.17.12.251 generated a Syslog Message.<br>\n"+
                " Node ID: 0<br>\n"+
                " Host: Unknown<br>\n"+
                " Interface: 172.17.12.251 <br>\n"+
                " Message: 172.17.12.251: Mar 5 20:48:35.644: %SSH-4-SSH2_UNEXPECTED_MSG: Unexpected message type has arrived. Terminating the connection <br>\n"+
                " Process: 304806 <br>\n"+
                " PID: \n"+
                " </p>\n"+
                "#/description#\n"+
                "41\n"+
                "Unknown\n"+
                "172.17.12.251\n"+
                "Warning\n"+
                "Thursday, March 5, 2015 2:48:47 PM CST\n"+
                ".\n"+
                "uei.opennms.org/syslogd/local7/Warning\n"+
                "syslogmessage=\"172.17.12.251: Mar 5 20:48:35.644: %SSH-4-SSH2_UNEXPECTED_MSG: Unexpected message type has arrived. Terminating the connection\" severity=\"Warning\" timestamp=\"Mar 05 14:48:47\" process=\"304806\" service=\"local7\""
                + "::1";
        assertEquals(validString, newString);
    }

    @Test
    public void testExpandParmsWithoutAnyParameterToExpand() {
        String input = "I am fine the way I am. I don't want to be expanded (-:";
        Assert.assertEquals(input, eventUtil.expandParms(input, m_svcLostEvent));
    }

    /**
     * Test method for extracting parm names rather than parm values
     */
    @Test
    public void testExpandParmNames() {
        String testString = "%uei%:%dpname%:%nodeid%:%parm[name-#1]%";
        
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        assertEquals("http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition::1:.1.3.6.1.2.1.15.3.1.7.128.64.32.16", newString);
    }

    /**
     * Test method for split-and-extract functionality indexed from beginning of name
     */
    @Test
    public void testSplitAndExtractParmNamePositive() {
        String testString = "%uei%:%dpname%:%nodeid%:%parm[name-#1.1]%.%parm[name-#1.3]%.%parm[name-#1.5]%.%parm[name-#1.7]%";
        
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        assertEquals("http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition::1:1.6.2.15", newString);
    }

    /**
     * Additional test method for split-and-extract functionality indexed from end of name
     */
    @Test
    public void testSplitAndExtractParmNameNegative() {
        String testString = "%uei%:%dpname%:%nodeid%:%parm[name-#1.-4]%.%parm[name-#1.-3]%.%parm[name-#1.-2]%.%parm[name-#1.-1]%";
        
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        assertEquals("http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition::1:128.64.32.16", newString);
    }
    
    /**
     * Test method for split-and-extract-range functionality indexed from beginning of name
     */
    @Test
    public void testSplitAndExtractParmNameRangePositive() {
        String testString = "%uei%:%dpname%:%nodeid%:%parm[name-#1.1:4]%";
        
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        assertEquals("http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition::1:1.3.6.1", newString);
    }
    
    /**
     * Test method for split-and-extract-range functionality indexed from beginning of name and extending to end
     */
    @Test
    public void testSplitAndExtractParmNameRangePositiveToEnd() {
        String testString = "%uei%:%dpname%:%nodeid%:%parm[name-#1.5:]%";
        
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        assertEquals("http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition::1:2.1.15.3.1.7.128.64.32.16", newString);
    }
    
    /**
     * Test method for split-and-extract-range functionality indexed from end of name
     */
    @Test
    public void testSplitAndExtractParmNameRangeNegative() {
        String testString = "%uei%:%dpname%:%nodeid%:%parm[name-#1.-4:2]%";
        
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        assertEquals("http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition::1:128.64", newString);
    }
    
    /**
     * Test method for split-and-extract-range functionality indexed from end of name and extending to end
     */
    @Test
    public void testSplitAndExtractParmNameRangeNegativeToEnd() {
        String testString = "%uei%:%dpname%:%nodeid%:%parm[name-#1.-5:]%";
        
        String newString = eventUtil.expandParms(testString, m_bgpBkTnEvent);
        assertEquals("http://uei.opennms.org/standards/rfc1657/traps/bgpBackwardTransition::1:7.128.64.32.16", newString);
    }
    
    /**
     * Test method for split-and-extract-range functionality indexed from end of name and extending to end
     */
    @Test
    public void testNodeFields() {
        String testString = "%uei%:%dpname%:%nodeid%:%nodelabel%:%foreignsource%:%foreignid%";

        String newString = eventUtil.expandParms(testString, m_svcLostEvent);
        assertEquals(EventConstants.NODE_LOST_SERVICE_EVENT_UEI + "::1:Unknown::", newString);
    }

    @Test
    public void testExpandTticketId() {
        String testString = "%tticketid%";
        String newString = eventUtil.expandParms(testString, m_nodeDownEvent);
        assertEquals("", newString);
        
        Tticket ticket = new Tticket();
        ticket.setContent("777");
        ticket.setState("1");
        m_nodeDownEvent.setTticket(ticket);
        newString = eventUtil.expandParms(testString, m_nodeDownEvent);
        assertEquals("777", newString);
    }

    @Test
    public void testNamedParameterExpansion() {
        // Create an expandable string that references N
        // distinct named parameters
        final int N = 6;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < N; i++) {
            sb.append("%parm[" + i + "]%");
        }
        String expandable = sb.toString();

        // Now create an event with N parameters matching
        // the names that are referenced above
        EventBuilder eb = new EventBuilder("uei", "test");
        for (int i = 0; i < N; i++) {
            // The named parameters in the expandable should
            // match the parameters name here even when we pad
            // them with whitespace
            eb.addParam(" \n\t" + i + "\n\t ", i);
        }
        Event e = eb.getEvent();

        // Manually generate the expected string
        sb = new StringBuilder();
        for (int i = 0; i < N; i++) {
            sb.append(i);
        }
        String expected = sb.toString();

        // Expand!
        assertEquals(expected, eventUtil.expandParms(expandable, e));

        final int M = 1000;
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            eventUtil.expandParms(expandable, e);
        }
        System.err.printf("Succesfully expanded %d events with %d parameters in %d ms\n",
                M, N, System.currentTimeMillis() - start);
    }
    
    @Test public void testSnmpV2DateAndTimeDecoding() {
        // See https://tools.ietf.org/html/rfc2579#page-18
        
        // Decode an SNMPv2-TC DateAndTime value which includes a time zone 
        Date dateWithTz = eventUtil.decodeSnmpV2TcDateAndTime(new BigInteger("07e2050b0d2a3a052d0000", 16));
        assertEquals("A valid DateAndTime with time zone should decode correctly", dateWithTz, new Date(1526046178500L));
        
        // Decode a DateAndTime which does not include a time zone
        Date dateWithoutTz = eventUtil.decodeSnmpV2TcDateAndTime(new BigInteger("07e2050b0d2a3a09", 16));
        assertEquals("A valid DateAndTime without time zone should decode correctly", dateWithoutTz, new Date(1526046178900L));
        
        // Decode an invalid DateAndTime (wrong length) and verify null is returned
        Date dateInvalid = eventUtil.decodeSnmpV2TcDateAndTime(new BigInteger("deadbeef", 16));
        assertNull("An invalid DateAndTime should return a null", dateInvalid);
    }

    @Test
    public void testClearDpName() throws Exception {
        final String minionId = UUID.randomUUID().toString();
        m_svcLostEvent.setDistPoller(minionId);
        String testString = new ExpandableParameter(AbstractEventUtil.TAG_DPNAME, eventUtil).expand(m_svcLostEvent, Maps.newHashMap());
        assertEquals("", testString);
    }
}
