package jnet.lib.snmp;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;

public class SNMPInterfaceScanner {

    private Snmp snmp;
    private String address;
    private String community;
    private int version;

    // OID pro rozhraní v IF-MIB
    private static final String IF_INDEX_OID = "1.3.6.1.2.1.2.2.1.1";   // ifIndex
    private static final String IF_DESCR_OID = "1.3.6.1.2.1.2.2.1.2";   // ifDescr
    private static final String IF_SPEED_OID = "1.3.6.1.2.1.2.2.1.5";       // speed
    private static final String IF_TYPE_OID = "1.3.6.1.2.1.2.2.1.3";    // ifType
    private static final String IF_MAC_OID = "1.3.6.1.2.1.2.2.1.3";         // mac
    private static final String IF_STATUS_OID = "1.3.6.1.2.1.2.2.1.8";  // ifOperStatus
    private static final String IF_STATUS_ADM_OID = "1.3.6.1.2.1.2.2.1.7";  // adminStatus
    private static final String IF_LAST_CHANGE_OID = "1.3.6.1.2.1.2.2.1.9"; // lastchange

    public SNMPInterfaceScanner(String address, String port, String community, int version) throws IOException {
        this.address = address + "/" + port;
        this.community = community;
        this.version = version;

        TransportMapping<UdpAddress> transport = new DefaultUdpTransportMapping();
        snmp = new Snmp(transport);
        transport.listen();
    }

    /**
     * Získání detailních informací o všech interfaces
     *
     * @throws IOException
     */
    public List<Interface> scanInterfaces() throws IOException {
        List<Interface> list = new ArrayList<>();
        
        CommunityTarget target = createTarget();

        OID oid = new OID(IF_INDEX_OID); // Start na ifIndex
        boolean finished = false;

        while (!finished) {
            PDU pdu = new PDU();
            pdu.add(new VariableBinding(oid));
            pdu.setType(PDU.GETNEXT);

            ResponseEvent response = snmp.send(pdu, target);
            if (response == null || response.getResponse() == null) {
                System.out.println("Žádná odpověď od SNMP agenta.");
                break;
            }

            VariableBinding vb = response.getResponse().get(0);
            OID nextOid = vb.getOid();

            // Pokud jsme mimo tabulku, ukončit cyklus
            if (!nextOid.startsWith(new OID(IF_INDEX_OID))) {
                finished = true;
                continue;
            }

            int ifIndex = vb.getVariable().toInt();
            String ifDescr = getSnmpValue(IF_DESCR_OID + "." + ifIndex, target);
            String ifType = getSnmpValue(IF_TYPE_OID + "." + ifIndex, target);
            String ifStatus = getSnmpValue(IF_STATUS_OID + "." + ifIndex, target);
            String ifSpeed = getSnmpValue(IF_SPEED_OID + "." + ifIndex, target);
            String ifMac = getSnmpValue(IF_MAC_OID + "." + ifIndex, target);
            String ifStatusAdm = getSnmpValue(IF_STATUS_ADM_OID + "." + ifIndex, target);
            String ifLastChange = getSnmpValue(IF_LAST_CHANGE_OID + "." + ifIndex, target);
            
            list.add(new Interface(
                    ifIndex, // index
                    ifDescr, // description
                    getInterfaceStatus(ifStatus), // operStatus
                    formatIfSpeed(Long.parseLong(ifSpeed)), // speed
                    getInterfaceType(ifType), // type
                    ifMac, //mac
                    convertIfAdminStatus(ifStatusAdm), //adminStatus
                    ifLastChange // lastChange
            ));

            // Posuneme OID na další položku
            oid = nextOid;
        }
        snmp.close();
        return list;
    }
    
    /**
     * Pomocná metoda pro vytvoření SNMP cíle (target)
     *
     * @return
     */
    private CommunityTarget createTarget() {
        CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString(community));
        target.setAddress(new UdpAddress(address));
        target.setVersion(SnmpConstants.version2c);
        target.setRetries(2);
        target.setTimeout(1500);
        return target;
    }

    private String getSnmpValue(String oid, CommunityTarget target) throws IOException {
        PDU pdu = new PDU();
        pdu.add(new VariableBinding(new OID(oid)));
        pdu.setType(PDU.GET);

        ResponseEvent response = snmp.send(pdu, target);
        if (response != null && response.getResponse() != null) {
            return response.getResponse().get(0).getVariable().toString();
        }
        return "Neznámé";
    }

    /**
     * prevadi ciselne oznaceni typu rozhrani na nazev
     * @param type
     * @return 
     */
    private String getInterfaceType(String type) {
        switch (type) {
            case "1":
                return "Jiné";
            case "6":
                return "Ethernet";
            case "7":
                return "ISO 8802-3";
            case "9":
                return "Token Ring";
            case "15":
                return "FDDI";
            case "23":
                return "PPP";
            case "24":
                return "Loopback";
            case "28":
                return "ATM";
            case "32":
                return "Frame Relay";
            case "37":
                return "IP-over-ATM";
            case "53":
                return "Fibre Channel";
            case "62":
                return "Fast Ethernet (100 Mbps)";
            case "69":
                return "Gigabit Ethernet (1 Gbps)";
            case "71":
                return "WiFi (802.11)";
            case "94":
                return "ATM Subinterface";
            case "117":
                return "Gigabit Ethernet (1 Gbps)";
            case "131":
                return "Tunnel";
            case "135":
                return "VLAN";
            case "136":
                return "LACP (Link Aggregation)";
            case "161":
                return "ISDN";
            case "169":
                return "DSL";
            case "175":
                return "DOCSIS (kabelový modem)";
            case "181":
                return "ATM (PON)";
            case "182":
                return "Ethernet (PON)";
            case "209":
                return "Bridge";
            case "237":
                return "LTE";
            case "243":
                return "5G";
            default:
                return "(" + type + ") Neznámé ";
        }
    }

    /**
     * prevadi ciselne oznaceni statusu rozhrani na nazev
     *
     * @param status
     * @return
     */
    private String getInterfaceStatus(String status) {
        switch (status) {
            case "1":
                return "Up";            // up(1)
            case "2":
                return "Down";          // down(2)
            case "3":
                return "Testing";              // testing(3)
            case "4":
                return "Unknown";            // unknown(4)
            case "5":
                return "Dormant";           // dormant(5)
            case "6":
                return "No present";         // notPresent(6)
            case "7":
                return "Lower Layer Down";  // lowerLayerDown(7)
            default:
                return "Neznámé (" + status + ")";
        }
    }

/**
     * Pomocná metoda pro rmátování času z Unixového formátu na čitelný
     *
     * @param unixTimestamp
     * @return
     */
    private String formatTimestamp(String unixTimestamp) {
        try {
            long timestamp = Long.parseLong(unixTimestamp);
            return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date(timestamp * 1000));
        } catch (NumberFormatException e) {
            return "";
        }
    }
    
    /**
     * funkce převede rychlost do citelnejsi podoby
     * @param ifSpeed
     * @return 
     */
    private static String formatIfSpeed(long ifSpeed) {
        String[] UNITS = {"bps", "kbps", "Mbps", "Gbps", "Tbps"};
        
        if (ifSpeed <= 0) {
            return "-";
        }

        int unitIndex = 0;
        double speed = ifSpeed;

        while (speed >= 1000 && unitIndex < UNITS.length - 1) {
            speed /= 1000;
            unitIndex++;
        }

        long roundedSpeed = Math.round(speed); // Zaokrouhlení na celé číslo
        DecimalFormat df = new DecimalFormat("#,###"); // Oddělování tisíců

        return df.format(roundedSpeed) + " " + UNITS[unitIndex];
    }

    /**
     * prevede adm status na text
     * @param status
     * @return 
     */
    private static String convertIfAdminStatus(String status) {
        return switch (status) {
            case "1" -> "Up";
            case "2" -> "Down";
            case "3" -> "Testing";
            default -> "Unknown";
        };
    }
}
