import asyncio
from datetime import datetime
import pytest
from pysnmp.hlapi.v1arch.asyncio import *
from pysnmp.proto.errind import RequestTimedOut
from pysnmp.proto.rfc1905 import errorStatus as pysnmp_errorStatus
from pysnmp.smi import builder, compiler, view

from tests.agent_context import AGENT_PORT, AgentContextManager


@pytest.mark.asyncio
async def test_v1_get():
    async with AgentContextManager():
        with SnmpDispatcher() as snmpDispatcher:
            iterator = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await UdpTransportTarget.create(("localhost", AGENT_PORT)),
                ("1.3.6.1.2.1.1.1.0", None),
            )

            errorIndication, errorStatus, errorIndex, varBinds = iterator

            assert errorIndication is None
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 1
            assert varBinds[0][0].prettyPrint() == "1.3.6.1.2.1.1.1.0"
            assert varBinds[0][1].prettyPrint().startswith("PySNMP engine version")

            name = pysnmp_errorStatus.namedValues.getName(errorStatus)
            assert name == "noError"


@pytest.mark.asyncio
async def test_v1_get_ipv6():
    async with AgentContextManager(enable_ipv6=True):
        with SnmpDispatcher() as snmpDispatcher:
            iterator = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await Udp6TransportTarget.create(("localhost", AGENT_PORT)),
                ("1.3.6.1.2.1.1.1.0", None),
            )

            errorIndication, errorStatus, errorIndex, varBinds = iterator

            assert errorIndication is None
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 1
            assert varBinds[0][0].prettyPrint() == "1.3.6.1.2.1.1.1.0"
            assert varBinds[0][1].prettyPrint().startswith("PySNMP engine version")

            name = pysnmp_errorStatus.namedValues.getName(errorStatus)
            assert name == "noError"


@pytest.mark.asyncio
async def test_v1_get_timeout_invalid_target():
    with SnmpDispatcher() as snmpDispatcher:

        async def run_get():
            errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                snmpDispatcher,
                CommunityData("community_string"),
                await UdpTransportTarget.create(("1.2.3.4", 161), timeout=1, retries=0),
                ObjectType(ObjectIdentity("1.3.6.1.4.1.60069.9.1.0")),
            )
            assert isinstance(errorIndication, RequestTimedOut)
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 0

        start = datetime.now()
        try:
            await asyncio.wait_for(run_get(), timeout=3)
            end = datetime.now()
            elapsed_time = (end - start).total_seconds()
            assert elapsed_time >= 1 and elapsed_time <= 3
        except asyncio.TimeoutError:
            assert False, "Test case timed out"


@pytest.mark.asyncio
async def test_v1_get_timeout_slow_object():
    async with AgentContextManager(enable_custom_objects=True):
        with SnmpDispatcher() as snmpDispatcher:

            async def run_get():
                errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                    snmpDispatcher,
                    CommunityData("public", mpModel=0),
                    await UdpTransportTarget.create(
                        ("localhost", AGENT_PORT), timeout=1, retries=0
                    ),
                    ObjectType(ObjectIdentity("1.3.6.1.4.1.60069.9.1.0")),
                )
                for varBind in varBinds:
                    print([str(varBind[0]), varBind[1]])

            start = datetime.now()
            try:
                await asyncio.wait_for(run_get(), timeout=3)
                end = datetime.now()
                elapsed_time = (end - start).total_seconds()
                assert elapsed_time >= 1 and elapsed_time <= 3
            except asyncio.TimeoutError:
                assert False, "Test case timed out"


@pytest.mark.asyncio
async def test_v1_get_no_access_object():
    async with AgentContextManager(enable_custom_objects=True):
        with SnmpDispatcher() as snmpDispatcher:
            errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await UdpTransportTarget.create(
                    ("localhost", AGENT_PORT), timeout=1, retries=0
                ),
                ObjectType(ObjectIdentity("1.3.6.1.4.1.60069.9.3")),
            )
            assert errorIndication is None
            assert (
                errorStatus.prettyPrint() == "noSuchName"
            )  # v1 does not have noAccess


@pytest.mark.asyncio
async def test_v1_get_dateandtime_object():
    async with AgentContextManager(enable_custom_objects=True):
        with SnmpDispatcher() as snmpDispatcher:
            # Step 1: Set up MIB builder and add custom MIB directory
            mibBuilder = builder.MibBuilder()
            compiler.addMibCompiler(mibBuilder)
            mibViewController = view.MibViewController(mibBuilder)

            # Load the custom MIB
            mibBuilder.loadModules("LEXTUDIO-TEST-MIB")
            snmpDispatcher.cache["mibViewController"] = mibViewController

            errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await UdpTransportTarget.create(
                    ("localhost", AGENT_PORT), timeout=1, retries=0
                ),
                ObjectType(
                    ObjectIdentity("LEXTUDIO-TEST-MIB", "testDateAndTime", 0)
                ),  # "1.3.6.1.4.1.60069.9.5.0"
            )
            assert errorIndication is None
            assert errorIndication is None
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 1
            assert (
                varBinds[0][0].prettyPrint() == "LEXTUDIO-TEST-MIB::testDateAndTime.0"
            )
            assert (
                varBinds[0][1].prettyPrint() == "2024-11-3,19:52:40.0,+0:0"
            )  # IMPORTANT: test DateAndTime display hint.


@pytest.mark.asyncio
async def test_v1_get_physaddress_object():
    async with AgentContextManager(enable_custom_objects=True):
        with SnmpDispatcher() as snmpDispatcher:
            # Step 1: Set up MIB builder and add custom MIB directory
            mibBuilder = builder.MibBuilder()
            compiler.addMibCompiler(mibBuilder)
            mibViewController = view.MibViewController(mibBuilder)

            # Load the custom MIB
            mibBuilder.loadModules("LEXTUDIO-TEST-MIB")
            snmpDispatcher.cache["mibViewController"] = mibViewController

            errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await UdpTransportTarget.create(
                    ("localhost", AGENT_PORT), timeout=1, retries=0
                ),
                ObjectType(
                    ObjectIdentity("LEXTUDIO-TEST-MIB", "testPhysAddress", 0)
                ),  # "1.3.6.1.4.1.60069.9.6.0"
            )
            assert errorIndication is None
            assert errorIndication is None
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 1
            assert (
                varBinds[0][0].prettyPrint() == "LEXTUDIO-TEST-MIB::testPhysAddress.0"
            )
            assert (
                varBinds[0][1].prettyPrint() == "00:11:22:33:44:55"
            )  # IMPORTANT: test PhysAddress display hint.


@pytest.mark.asyncio
async def test_v1_get_scaled_integer_object():
    async with AgentContextManager(enable_custom_objects=True):
        with SnmpDispatcher() as snmpDispatcher:
            # Step 1: Set up MIB builder and add custom MIB directory
            mibBuilder = builder.MibBuilder()
            compiler.addMibCompiler(mibBuilder)
            mibViewController = view.MibViewController(mibBuilder)

            # Load the custom MIB
            mibBuilder.loadModules("LEXTUDIO-TEST-MIB")
            snmpDispatcher.cache["mibViewController"] = mibViewController

            errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await UdpTransportTarget.create(
                    ("localhost", AGENT_PORT), timeout=1, retries=0
                ),
                ObjectType(
                    ObjectIdentity("LEXTUDIO-TEST-MIB", "testScaledInteger", 0)
                ),  # "1.3.6.1.4.1.60069.9.7.0"
            )
            assert errorIndication is None
            assert errorIndication is None
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 1
            assert (
                varBinds[0][0].prettyPrint() == "LEXTUDIO-TEST-MIB::testScaledInteger.0"
            )
            assert (
                varBinds[0][1].prettyPrint() == "5.0"
            )  # IMPORTANT: test display hint "d-1".


@pytest.mark.asyncio
async def test_v1_get_scaled_unsigned_object():
    async with AgentContextManager(enable_custom_objects=True):
        with SnmpDispatcher() as snmpDispatcher:
            # Step 1: Set up MIB builder and add custom MIB directory
            mibBuilder = builder.MibBuilder()
            compiler.addMibCompiler(mibBuilder)
            mibViewController = view.MibViewController(mibBuilder)

            # Load the custom MIB
            mibBuilder.loadModules("LEXTUDIO-TEST-MIB")
            snmpDispatcher.cache["mibViewController"] = mibViewController

            errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await UdpTransportTarget.create(
                    ("localhost", AGENT_PORT), timeout=1, retries=0
                ),
                ObjectType(
                    ObjectIdentity("LEXTUDIO-TEST-MIB", "testScaledUnsigned", 0)
                ),  # "1.3.6.1.4.1.60069.9.8.0"
            )
            assert errorIndication is None
            assert errorIndication is None
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 1
            assert (
                varBinds[0][0].prettyPrint()
                == "LEXTUDIO-TEST-MIB::testScaledUnsigned.0"
            )
            assert (
                varBinds[0][1].prettyPrint() == "0.5"  # GitHub issue #139
            )  # IMPORTANT: test display hint "d-1".


@pytest.mark.asyncio
async def test_v1_get_multiple_objects():
    async with AgentContextManager(enable_custom_objects=True):
        with SnmpDispatcher() as snmpDispatcher:
            # # Step 1: Set up MIB builder and add custom MIB directory
            # mibBuilder = builder.MibBuilder()
            # compiler.addMibCompiler(mibBuilder)
            # mibViewController = view.MibViewController(mibBuilder)

            # # Load the custom MIB
            # mibBuilder.loadModules("LEXTUDIO-TEST-MIB")
            # snmpDispatcher.cache["mibViewController"] = mibViewController

            netiftable = [
                ObjectIdentity("IF-MIB", "ifDescr", 1),
                ObjectIdentity("IF-MIB", "ifDescr", 2),
            ]

            errorIndication, errorStatus, errorIndex, varBinds = await get_cmd(
                snmpDispatcher,
                CommunityData("public", mpModel=0),
                await UdpTransportTarget.create(
                    ("demo.pysnmp.com", 161), timeout=1, retries=0
                ),
                *[ObjectType(x) for x in netiftable],
            )
            assert errorIndication is None
            assert errorIndication is None
            assert errorStatus == 0
            assert errorIndex == 0
            assert len(varBinds) == 2
            assert varBinds[0][1].prettyPrint() != varBinds[1][1].prettyPrint()
