/*_############################################################################
  _## 
  _##  SNMP4J - SnmpBuilderTest.java  
  _## 
  _##  Copyright (C) 2003-2026  Frank Fock (SNMP4J.org)
  _##  
  _##  Licensed 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.snmp4j.fluent;

import org.junit.*;
import org.snmp4j.*;
import org.snmp4j.log.ConsoleLogAdapter;
import org.snmp4j.log.ConsoleLogFactory;
import org.snmp4j.log.LogAdapter;
import org.snmp4j.log.LogFactory;
import org.snmp4j.mp.CounterSupport;
import org.snmp4j.mp.DefaultCounterListener;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.transport.TransportMappings;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;

import static org.junit.Assert.*;

public class SnmpBuilderTest {

    static {
        LogFactory.setLogFactory(new ConsoleLogFactory());
        ConsoleLogAdapter.setDebugEnabled(true);
    }

    private static final LogAdapter LOGGER = LogFactory.getLogger(SnmpBuilderTest.class);

    public static final String PRIV_PASSPHRASE_1 = "_098765sjldgh4+*`'ß321_";
    public static final String AUTH_PASSPHRASE_1 = "_123woieotuwoi*#etz45678_";
    public static final String PRIV_PASSPHRASE_2 = "öä!4§98765sjldgh4+*`'ß321_";
    public static final String AUTH_PASSPHRASE_2 = "&/()oieotuwoi*#etz45678_";
    public static final String SHA_256_AES_128 = "SHA256AES128-username1";
    public static final String SHA_224_AES_128 = "SHA224AES128-username2";
    public static final String UNKNOWN_USER = "unknownUser";

    private static final String LOCAL_HOST = "127.0.0.1";
    private static final int RESPONDER_PORT = 161;

    private static final  Map<OID, Variable> TEST_VALUES = new HashMap<OID, Variable>() {{
        put(new OID(SnmpConstants.sysDescr), new OctetString("Test System Description"));
        put(new OID(SnmpConstants.sysObjectID), new OID("1.3.6.1.4.1.12345"));
        put(new OID(SnmpConstants.sysUpTime), new TimeTicks(12345));
        put(new OID(SnmpConstants.sysContact), new OctetString("Test Contact"));
        put(new OID(SnmpConstants.sysName), new OctetString("Test System"));
        put(new OID(SnmpConstants.sysLocation), new OctetString("Test Location"));
        put(new OID(SnmpConstants.sysServices), new Integer32(72));
    }};


    private DefaultUdpTransportMapping transportMappingCG;
    private DefaultUdpTransportMapping transportMappingCR;
    private Snmp snmpCommandGenerator;
    private Snmp snmpCommandResponder;

    @BeforeClass
    public static void setupBeforeClass() throws Exception {
        SecurityProtocols.getInstance().addDefaultProtocols();
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        SecurityProtocols.setSecurityProtocols(null);
        System.clearProperty(TransportMappings.TRANSPORT_MAPPINGS);
    }

    @Before
    public void setUp() throws Exception {
        initializeTransports();
        initializeCommandResponder();
        addDefaultUsers();
    }

    private void initializeTransports() throws Exception {
        UdpAddress generatorAddress = new UdpAddress(LOCAL_HOST + "/0");
        UdpAddress responderAddress = new UdpAddress(LOCAL_HOST + "/0");
        
        transportMappingCG = new DefaultUdpTransportMapping(generatorAddress);
        transportMappingCR = new DefaultUdpTransportMapping(responderAddress);
        transportMappingCR.listen();
    }

    private void configureMPv3Generator(MPv3 mpv3) {
        mpv3.setLocalEngineID(MPv3.createLocalEngineID(new OctetString("generator")));
        mpv3.setCurrentMsgID(MPv3.randomMsgID(new Random().nextInt(MPv3.MAX_MESSAGE_ID)));
        
        SecurityModels.getInstance().addSecurityModel(
                new USM(SecurityProtocols.getInstance(), 
                new OctetString(mpv3.getLocalEngineID()), 0));
    }

    private void initializeCommandResponder() {
        snmpCommandResponder = new Snmp(transportMappingCR);
        CounterSupport counterSupportResponder = new CounterSupport();
        snmpCommandResponder.setCounterSupport(counterSupportResponder);

        setupCounterListeners(counterSupportResponder);
        configureMPv3Responder(counterSupportResponder);
    }

    private void setupCounterListeners(CounterSupport counterSupportResponder) {
        counterSupportResponder.addCounterListener(new DefaultCounterListener());
        CounterSupport.getInstance().addCounterListener(new DefaultCounterListener());
    }

    private void configureMPv3Responder(CounterSupport counterSupportResponder) {
        SecurityModels respSecModels = new SecurityModels() {};
        MPv3 mpv3Responder = (MPv3) snmpCommandResponder.getMessageDispatcher()
                .getMessageProcessingModel(MPv3.ID);
        
        mpv3Responder.setCounterSupport(counterSupportResponder);
        mpv3Responder.setLocalEngineID(MPv3.createLocalEngineID(new OctetString("responder")));
        
        USM usmResponder = new USM(SecurityProtocols.getInstance(), 
                new OctetString(mpv3Responder.getLocalEngineID()), 0);
        usmResponder.setCounterSupport(counterSupportResponder);
        
        respSecModels.addSecurityModel(usmResponder);
        mpv3Responder.setSecurityModels(respSecModels);
    }

    private void addDefaultUsers() {
        OctetString longUsername = new OctetString(new byte[32]);
        Arrays.fill(longUsername.getValue(), (byte) 0x20);
        //addCommandGeneratorUsers(longUsername);
        addCommandResponderUsers(longUsername);
    }

    private void addCommandResponderUsers(OctetString longUsername) {
        snmpCommandResponder.getUSM().addUser(
                new UsmUser(new OctetString(SHA_256_AES_128), AuthHMAC192SHA256.ID, new OctetString(AUTH_PASSPHRASE_1),
                        PrivAES128.ID, new OctetString(PRIV_PASSPHRASE_1)));
        snmpCommandResponder.getUSM().addUser(
                new UsmUser(new OctetString(SHA_224_AES_128), AuthHMAC128SHA224.ID, new OctetString(AUTH_PASSPHRASE_1),
                        PrivAES128.ID, new OctetString(PRIV_PASSPHRASE_1)));
        snmpCommandResponder.getUSM().addUser(
                new UsmUser(longUsername, AuthHMAC256SHA384.ID, new OctetString(AUTH_PASSPHRASE_2),
                        PrivAES256.ID, new OctetString(PRIV_PASSPHRASE_2)));
    }

    private void addCommandGeneratorUsers(OctetString longUsername) {
        snmpCommandGenerator.getUSM().addUser(
                new UsmUser(new OctetString(SHA_256_AES_128), AuthHMAC192SHA256.ID, new OctetString(AUTH_PASSPHRASE_1),
                        PrivDES.ID, new OctetString(PRIV_PASSPHRASE_1)));
        snmpCommandGenerator.getUSM().addUser(
                new UsmUser(new OctetString(UNKNOWN_USER), AuthHMAC128SHA224.ID, new OctetString(AUTH_PASSPHRASE_1),
                        PrivDES.ID, new OctetString(PRIV_PASSPHRASE_1)));
        snmpCommandGenerator.getUSM().addUser(
                new UsmUser(longUsername, AuthHMAC256SHA384.ID, new OctetString(AUTH_PASSPHRASE_2),
                        PrivDES.ID, new OctetString(PRIV_PASSPHRASE_2)));
    }

    @After
    public void tearDown() throws Exception {
         snmpCommandResponder.close();
    }

    @Test
    public void build() {
        
    }
    
    @Test
    public void nextFluentWithLateLocalization() throws IOException {
        SnmpBuilder snmpBuilder = new SnmpBuilder();
        Snmp snmp = snmpBuilder.tm(transportMappingCG).v3().usm().threads(2).build();
        snmp.listen();
        Address targetAddress = transportMappingCR.getListenAddress();
        TargetBuilder<?> targetBuilder = snmpBuilder.target(targetAddress);
        Target<?> userTarget = targetBuilder
                .user(SHA_256_AES_128).deferLocalizationUntilFirstUsage()
                .auth(TargetBuilder.AuthProtocol.hmac192sha256).authPassphrase(AUTH_PASSPHRASE_1)
                .priv(TargetBuilder.PrivProtocol.aes128).privPassphrase(PRIV_PASSPHRASE_1)
                .done()
                .timeout(500).retries(1)
                .build();
        PDU pdu = targetBuilder.pdu().type(PDU.GETNEXT).oids(SnmpConstants.sysDescr.toDottedString()).contextName("").build();

        // mockup code
        pdu.setRequestID(new Integer32(snmp.getNextRequestID()));
        Map<Integer, List<SnmpTest.RequestResponse>> queue = new HashMap<>(2);
        queue.computeIfAbsent(pdu.getRequestID().getValue(),
                k -> new ArrayList<>()).add(new SnmpTest.RequestResponse(pdu, makeResponse(pdu, userTarget.getVersion())));
        SnmpTest.TestCommandResponder responder = new SnmpTest.TestCommandResponder(snmpCommandResponder, queue);
        snmpCommandResponder.addCommandResponder(responder);
        snmpCommandResponder.listen();

        SnmpCompletableFuture snmpRequestFuture = SnmpCompletableFuture.send(snmp, userTarget, pdu);
        try {
            List<VariableBinding> vbs = snmpRequestFuture.get().getAll();
//            System.out.println("Received: " + snmpRequestFuture.getResponseEvent().getResponse());
//            System.out.println("Payload:  " + vbs);
            assertEquals(1, vbs.size());
            assertEquals(SnmpConstants.sysDescr, vbs.get(0).getOid());
            assertEquals(TEST_VALUES.get(SnmpConstants.sysDescr), vbs.get(0).getVariable());
        } catch (ExecutionException | InterruptedException ex) {
            if (ex.getCause() != null) {
                System.err.println(ex.getCause().getMessage());
            } else {
                System.err.println("Request failed: "+ex.getMessage());
            }
        }
        snmp.close();
    }

    @Test
    public void nextFluentWithEarlyLocalization() throws IOException {
        snmpCommandResponder.getUSM().setEngineDiscoveryEnabled(true);
        SnmpBuilder snmpBuilder = new SnmpBuilder();
        Snmp snmp = snmpBuilder.tm(transportMappingCG).v3().usm().threads(2).build();
        snmp.listen();
        Address targetAddress = transportMappingCR.getListenAddress();
        TargetBuilder<?> targetBuilder = snmpBuilder.target(targetAddress);
        Target<?> userTarget = targetBuilder
                .user(SHA_256_AES_128, snmpCommandResponder.getLocalEngineID())
                .auth(TargetBuilder.AuthProtocol.hmac192sha256).authPassphrase(AUTH_PASSPHRASE_1)
                .priv(TargetBuilder.PrivProtocol.aes128).privPassphrase(PRIV_PASSPHRASE_1)
                .done()
                .timeout(5000).retries(0)
                .build();
        PDU pdu = targetBuilder.pdu().type(PDU.GETNEXT).oids(SnmpConstants.sysDescr.toDottedString()).contextName("").build();

        // mockup code
        pdu.setRequestID(new Integer32(snmp.getNextRequestID()));
        Map<Integer, List<SnmpTest.RequestResponse>> queue = new HashMap<>(2);
        queue.computeIfAbsent(pdu.getRequestID().getValue(),
                k -> new ArrayList<>()).add(new SnmpTest.RequestResponse(pdu, makeResponse(pdu, userTarget.getVersion())));
        SnmpTest.TestCommandResponder responder = new SnmpTest.TestCommandResponder(snmpCommandResponder, queue);
        snmpCommandResponder.addCommandResponder(responder);
        snmpCommandResponder.listen();

        SnmpCompletableFuture snmpRequestFuture = SnmpCompletableFuture.send(snmp, userTarget, pdu);
        try {
            List<VariableBinding> vbs = snmpRequestFuture.get().getAll();
//            System.out.println("Received: " + snmpRequestFuture.getResponseEvent().getResponse());
//            System.out.println("Payload:  " + vbs);
            assertEquals(1, vbs.size());
            assertEquals(SnmpConstants.sysDescr, vbs.get(0).getOid());
            assertEquals(TEST_VALUES.get(SnmpConstants.sysDescr), vbs.get(0).getVariable());
        } catch (ExecutionException | InterruptedException ex) {
            if (ex.getCause() != null) {
                System.err.println(ex.getCause().getMessage());
            } else {
                System.err.println("Request failed: "+ex.getMessage());
            }
            fail(ex.getMessage());
        }
        snmp.close();
    }

    private PDU makeResponse(PDU pdu, int version) {
        PDU responsePDU = (PDU) pdu.clone();
        responsePDU.setType(PDU.RESPONSE);
        responsePDU.setErrorStatus(PDU.noError);
        responsePDU.setErrorIndex(0);
        responsePDU.getVariableBindings().clear();
        for (VariableBinding vb : pdu.getVariableBindings()) {
            responsePDU.add(new VariableBinding(vb.getOid(), TEST_VALUES.get(vb.getOid())));
        }
        return responsePDU;
    }

}