import pytest

from inventory_monitor.snmp import (
    SROS_HW_CLASS_CHASSIS,
    SROS_HW_CLASS_MODULE,
    ISnmp,
    SnmpCisco,
    SnmpJunos,
    SnmpSros,
    SnmpCiscoLegacy,
    sanitize_snmp_error,
    get_snmp_handler,
    CISCO_ENTITY_FIELD,
    JUNOS_CHASSIS_FIELD,
    JUNOS_COMPONENT_FIELD,
    SROS_CHASSIS_FIELD,
)
from inventory_monitor.models import SnmpConfig


class TestIsValidSerial:
    def test_valid_serial(self):
        assert ISnmp.is_valid_serial("ABC1234") is True

    def test_short_serial(self):
        assert ISnmp.is_valid_serial("AB") is False

    def test_empty_serial(self):
        assert not ISnmp.is_valid_serial("")

    def test_none_serial(self):
        assert not ISnmp.is_valid_serial(None)

    def test_builtin(self):
        assert ISnmp.is_valid_serial("BUILTIN") is False
        assert ISnmp.is_valid_serial("  builtin  ") is False

    def test_whitespace_only(self):
        assert ISnmp.is_valid_serial("   ") is False

    def test_custom_min_length(self):
        assert ISnmp.is_valid_serial("AB", min_length=2) is True
        assert ISnmp.is_valid_serial("A", min_length=2) is False


class TestSanitizeSnmpError:
    def test_normal_error(self):
        err = Exception("Timeout occurred")
        assert sanitize_snmp_error(err) == "Timeout occurred"

    def test_error_with_password(self):
        err = Exception("auth_password mismatch for user")
        result = sanitize_snmp_error(err)
        assert "redacted" in result

    def test_error_with_community(self):
        err = Exception("community string invalid")
        result = sanitize_snmp_error(err)
        assert "redacted" in result


class TestGetSnmpHandler:
    def test_default_handler(self):
        handler = get_snmp_handler(None)
        assert isinstance(handler, SnmpCisco)

    def test_sros_handler(self):
        class FakePlatform:
            name = "sros"
        handler = get_snmp_handler(FakePlatform())
        assert isinstance(handler, SnmpSros)

    def test_junos_handler(self):
        class FakePlatform:
            name = "junos"
        handler = get_snmp_handler(FakePlatform())
        assert isinstance(handler, SnmpJunos)

    def test_unknown_platform_defaults_to_cisco(self):
        class FakePlatform:
            name = "unknown_os"
        handler = get_snmp_handler(FakePlatform())
        assert isinstance(handler, SnmpCisco)

    def test_accepts_snmp_config(self):
        cfg = SnmpConfig(versions=[2], timeout=30)
        handler = get_snmp_handler(None, snmp_config=cfg)
        assert handler.snmp_config.timeout == 30


class TestAddToInventory:
    def test_adds_new_key(self):
        inv = {}
        ISnmp._add_to_inventory(inv, "1", "field_a", "val")
        assert inv == {"1": {"field_a": "val"}}

    def test_adds_to_existing_key(self):
        inv = {"1": {"field_a": "val"}}
        ISnmp._add_to_inventory(inv, "1", "field_b", "val2")
        assert inv == {"1": {"field_a": "val", "field_b": "val2"}}


class TestCiscoHumanReadable:
    def test_filters_invalid_serials(self):
        handler = SnmpCisco()
        f = CISCO_ENTITY_FIELD
        inventory = {
            "1": {f["SERIAL_NUM"]: "VALID123", f["CLASS"]: "chassis", f["DESCR"]: "Test"},
            "2": {f["SERIAL_NUM"]: "AB", f["CLASS"]: "module"},  # too short
            "3": {f["SERIAL_NUM"]: "BUILTIN", f["CLASS"]: "module"},
        }
        result = handler.get_human_readable_inventory(inventory)
        assert len(result) == 1
        assert result[0]["entPhysicalSerialNum"] == "VALID123"

    def test_empty_inventory(self):
        handler = SnmpCisco()
        result = handler.get_human_readable_inventory({})
        assert result == []


class TestSrosHwClassConstants:
    def test_chassis_set(self):
        assert 3 in SROS_HW_CLASS_CHASSIS

    def test_module_set(self):
        assert 8 in SROS_HW_CLASS_MODULE
        assert 9 in SROS_HW_CLASS_MODULE
        assert 10 in SROS_HW_CLASS_MODULE
        assert 11 in SROS_HW_CLASS_MODULE


class TestSrosHumanReadable:
    def test_chassis_item(self):
        handler = SnmpSros()
        sf = SROS_CHASSIS_FIELD
        inventory = {
            "1": {
                sf["SERIAL"]: "CHASSIS123",
                sf["HW_CLASS"]: "3",
                sf["MODEL"]: "7750-SR",
                sf["COMPONENT_NAME"]: "Chassis",
            }
        }
        result = handler.get_human_readable_inventory(inventory)
        assert len(result) == 1
        assert result[0]["entPhysicalClass"] == "chassis"
        assert result[0]["entPhysicalSerialNum"] == "CHASSIS123"

    def test_module_item(self):
        handler = SnmpSros()
        sf = SROS_CHASSIS_FIELD
        inventory = {
            "1": {
                sf["SERIAL"]: "MOD12345",
                sf["HW_CLASS"]: "8",
                sf["MODEL"]: "iom-card",
                sf["COMPONENT_NAME"]: "IOM 1",
            }
        }
        result = handler.get_human_readable_inventory(inventory)
        assert len(result) == 1
        assert result[0]["entPhysicalClass"] == "-"

    def test_port_item(self):
        handler = SnmpSros()
        sf = SROS_CHASSIS_FIELD
        inventory = {
            "port-1": {
                sf["PORT_SERIAL"]: "PORT1234",
                sf["PORT_MODEL"]: "SFP-1000",
                sf["PORT_NAME"]: "1/1/1",
                sf["PORT_TYPE"]: "3",
            }
        }
        result = handler.get_human_readable_inventory(inventory)
        assert len(result) == 1
        assert result[0]["entPhysicalClass"] == "port"
        assert result[0]["entPhysicalDescr"] == "SFP transceiver"

    def test_unknown_hw_class_skipped(self):
        handler = SnmpSros()
        sf = SROS_CHASSIS_FIELD
        inventory = {
            "1": {
                sf["SERIAL"]: "SKIP1234",
                sf["HW_CLASS"]: "99",
                sf["MODEL"]: "unknown",
                sf["COMPONENT_NAME"]: "Unknown",
            }
        }
        result = handler.get_human_readable_inventory(inventory)
        assert len(result) == 0


class TestJunosHumanReadable:
    def test_chassis_and_component(self):
        handler = SnmpJunos()
        cf = JUNOS_CHASSIS_FIELD
        jf = JUNOS_COMPONENT_FIELD
        inventory = {
            "0": {cf["NAME"]: "Chassis", cf["SERIAL"]: "JUNOS123"},
            "cont-1.0": {
                jf["DESCR"]: "FPC 0",
                jf["SERIAL"]: "COMP1234",
                jf["PART_NUM"]: "750-12345",
            },
        }
        result = handler.get_human_readable_inventory(inventory)
        assert len(result) == 2
        serials = {r["entPhysicalSerialNum"] for r in result}
        assert serials == {"JUNOS123", "COMP1234"}


class TestCiscoLegacyHumanReadable:
    def test_chassis_and_slot(self):
        handler = SnmpCiscoLegacy()
        inventory = {
            "0": {"0": "LEG12345"},
            "cont-1": {"4": "SLOT1234", "3": "WS-X6748", "7": "1"},
        }
        result = handler.get_human_readable_inventory(inventory)
        assert len(result) == 2
        chassis = [r for r in result if r["entPhysicalClass"] == "chassis"]
        assert chassis[0]["entPhysicalSerialNum"] == "LEG12345"
