"""
Proxy classes for network-poller protobuf messages.
These classes allow bounds checking and error handling to be centralized.
"""
import base64
from collections import namedtuple
from datetime import timezone
import json
import logging
from preseem_protobuf.network_poller import network_poller_pb2
from uuid import UUID

from google.protobuf.json_format import MessageToDict


class NetworkElementUpdateException(Exception):
    """Allows a poll operation to raise an error for the NetworkElement."""

    def __init__(self, error, msg):
        self.error = error
        super().__init__(msg)


class ErrorContext:
    """Container for holding errors during protobuf operations."""
    Error = namedtuple('Error', ('code', 'msg', 'args'))

    def __init__(self):
        self.errors = []

    def add(self, code, msg, **kwargs):
        """Add an error."""
        self.errors.append(self.Error(code, msg, kwargs))

    def diff(self, other):
        """Return a list of errors that are in other but not in this context."""
        return [x for x in other.errors if x not in self.errors]


class Repeated:

    def __init__(self, py_type, pb_field, errctx, checker=None):
        self.py_type = py_type
        self.pb_field = pb_field
        self.errctx = errctx
        self.checker = checker

    def __iter__(self):
        return iter([self.py_type(pb) if self.py_type else pb for pb in self.pb_field])

    def __len__(self):
        return len(self.pb_field)

    def __getitem__(self, i):
        return self.py_type(self.pb_field[i]) if self.py_type else self.pb_field[i]

    def __delitem__(self, i):
        del self.pb_field[i]

    def add(self):
        assert self.py_type  # this method only works for message fields
        pb = self.pb_field.add()
        return self.py_type(pb, errctx=self.errctx)

    def append(self, value):
        if not self.checker or self.checker(value):
            if self.py_type and isinstance(value, self.py_type):
                self.pb_field.append(value.pb)
            else:
                self.pb_field.append(value)


class BaseWrapperClass:
    """A base class for protobuf wrapper classes"""

    def __init__(self, pb=None, errctx=None):
        self.pb = pb if pb else self.PBTYPE()
        self.errctx = errctx

    def __eq__(self, other):
        """define equivalence as the equivalence of the wrapped protobuf."""
        if not isinstance(other, type(self)):
            return NotImplemented
        return self.pb == other.pb

    def __repr__(self):
        """repr function for pb wrapper classes.  Requires _attrs to be defined."""
        attr_values = [(attr, getattr(self, attr)) for attr in self._attrs]
        attributes_str = ', '.join(f"{attr}={value!r}" for attr, value in attr_values)
        return f"{type(self).__name__}({attributes_str})"

    def pretty(self):
        """return a pretty-printed string representing this full message."""

        def unencode(d):
            for a, v in d.items():
                if a.endswith('uuid'):
                    try:
                        d[a] = str(UUID(bytes=base64.b64decode(v)))
                    except ValueError:
                        d[a] = v
                elif a == 'poller_hash':
                    v = base64.b64decode(v)
                    if len(v) == 32:
                        # assume sha256 poller hash
                        d[a] = v.hex()
                    else:
                        # likely an intermediate value, show as a string
                        # (can't convert bytes to json)
                        d[a] = v.decode()
                elif isinstance(v, dict):
                    unencode(v)
                elif isinstance(v, list):
                    for x in v:
                        if isinstance(x, dict):
                            unencode(x)

        m = MessageToDict(self.pb, preserving_proto_field_name=True)
        unencode(m)
        return json.dumps(m, indent=2)


def pb_wrapper(pbtype):
    """Class decorator that sets the PBTYPE and creates an _attrs member."""

    def class_decorator(cls):
        setattr(cls, 'PBTYPE', pbtype)
        cls._attrs = [
            name for name, value in vars(cls).items() if isinstance(value, property)
        ]
        return cls

    return class_decorator


@pb_wrapper(network_poller_pb2.NetworkElement.Link)
class Link(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.bridged_mac_addresses = Repeated(None, self.pb.bridged_mac_addresses,
                                              self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def oper_up(self):
        return self.pb.oper_up if self.pb.HasField('oper_up') else None

    @oper_up.setter
    def oper_up(self, value):
        if value is not None:
            self.pb.oper_up = value
        else:
            self.pb.ClearField('oper_up')

    @property
    def mac_address(self):
        return self.pb.mac_address

    @mac_address.setter
    def mac_address(self, value):
        if value is not None:
            self.pb.mac_address = value

    @property
    def in_octets(self):
        return self.pb.in_octets if self.pb.HasField('in_octets') else None

    @in_octets.setter
    def in_octets(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_octets = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.in_octets',
                                value=value,
                                metric_name='NetworkElement.Link.in_octets')
        else:
            self.pb.ClearField('in_octets')

    @property
    def in_ucast_pkts(self):
        return self.pb.in_ucast_pkts if self.pb.HasField('in_ucast_pkts') else None

    @in_ucast_pkts.setter
    def in_ucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_ucast_pkts = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.in_ucast_pkts',
                                value=value,
                                metric_name='NetworkElement.Link.in_ucast_pkts')
        else:
            self.pb.ClearField('in_ucast_pkts')

    @property
    def in_nucast_pkts(self):
        return self.pb.in_nucast_pkts if self.pb.HasField('in_nucast_pkts') else None

    @in_nucast_pkts.setter
    def in_nucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_nucast_pkts = value
            else:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.in_nucast_pkts',
                                value=value,
                                metric_name='NetworkElement.Link.in_nucast_pkts')
        else:
            self.pb.ClearField('in_nucast_pkts')

    @property
    def in_discards(self):
        return self.pb.in_discards if self.pb.HasField('in_discards') else None

    @in_discards.setter
    def in_discards(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_discards = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.in_discards',
                                value=value,
                                metric_name='NetworkElement.Link.in_discards')
        else:
            self.pb.ClearField('in_discards')

    @property
    def in_errors(self):
        return self.pb.in_errors if self.pb.HasField('in_errors') else None

    @in_errors.setter
    def in_errors(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_errors = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.in_errors',
                                value=value,
                                metric_name='NetworkElement.Link.in_errors')
        else:
            self.pb.ClearField('in_errors')

    @property
    def in_unknown_protos(self):
        return self.pb.in_unknown_protos if self.pb.HasField(
            'in_unknown_protos') else None

    @in_unknown_protos.setter
    def in_unknown_protos(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_unknown_protos = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Link.in_unknown_protos',
                    value=value,
                    metric_name=' NetworkElement.Link.in_unknown_protos')
        else:
            self.pb.ClearField('in_unknown_protos')

    @property
    def out_octets(self):
        return self.pb.out_octets if self.pb.HasField('out_octets') else None

    @out_octets.setter
    def out_octets(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_octets = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.out_octets',
                                value=value,
                                metric_name="NetworkElement.Link.out_octets")
        else:
            self.pb.ClearField('out_octets')

    @property
    def out_ucast_pkts(self):
        return self.pb.out_ucast_pkts if self.pb.HasField('out_ucast_pkts') else None

    @out_ucast_pkts.setter
    def out_ucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_ucast_pkts = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.out_ucast_pkts',
                                value=value,
                                metric_name='NetworkElement.Link.out_ucast_pkts')
        else:
            self.pb.ClearField('out_ucast_pkts')

    @property
    def out_nucast_pkts(self):
        return self.pb.out_nucast_pkts if self.pb.HasField('out_nucast_pkts') else None

    @out_nucast_pkts.setter
    def out_nucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_nucast_pkts = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.out_nucast_pkts',
                                value=value,
                                metric_name='NetworkElement.Link.out_nucast_pkts')
        else:
            self.pb.ClearField('out_nucast_pkts')

    @property
    def out_discards(self):
        return self.pb.out_discards if self.pb.HasField('out_discards') else None

    @out_discards.setter
    def out_discards(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_discards = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.out_discards',
                                value=value,
                                metric_name='NetworkElement.Link.out_discards')
        else:
            self.pb.ClearField('out_discards')

    @property
    def out_errors(self):
        return self.pb.out_errors if self.pb.HasField('out_errors') else None

    @out_errors.setter
    def out_errors(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_errors = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.out_errors',
                                value=value,
                                metric_name='NetworkElement.Link.out_errors')
        else:
            self.pb.ClearField('out_errors')

    @property
    def out_retransmits(self):
        return self.pb.out_retransmits if self.pb.HasField('out_retransmits') else None

    @out_retransmits.setter
    def out_retransmits(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_retransmits = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Link.out_retransmits',
                                value=value,
                                metric_name='NetworkElement.Link.out_retransmits')
            else:
                self.pb.ClearField('out_retransmits')

    @property
    def poller_hash(self):
        return self.pb.poller_hash if self.pb.HasField('poller_hash') else None

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value

    def add_bridged_mac(self, mac):
        if mac.startswith('33:33:') or mac == '00:00:00:00:00:00':
            return  # ignore IPv6 multicast MACs (PPA-1321) and zero MACs
        self.bridged_mac_addresses.append(mac)


@pb_wrapper(network_poller_pb2.NetworkElement.WirelessLink)
class WirelessLink(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def mac_address(self):
        return self.pb.mac_address

    @mac_address.setter
    def mac_address(self, value):
        if value is not None:
            self.pb.mac_address = value

    @property
    def distance(self):
        return self.pb.distance if self.pb.HasField('distance') else None

    @distance.setter
    def distance(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.distance = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.WirelessLink.distance',
                    value=value,
                    metric_name='NetworkElement.WirelessLink.distance')
        else:
            self.pb.ClearField('distance')

    @property
    def connected_time(self):
        return self.pb.connected_time if self.pb.HasField('connected_time') else None

    @connected_time.setter
    def connected_time(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.connected_time = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.WirelessLink.connected_time',
                    value=value,
                    metric_name='NetworkElement.WirelessLink.connected_time')
        else:
            self.pb.ClearField('connected_time')

    @property
    def noise_floor(self):
        return self.pb.noise_floor if self.pb.HasField('noise_floor') else None

    @noise_floor.setter
    def noise_floor(self, value):
        if value is not None:
            self.pb.noise_floor = value
        else:
            self.pb.ClearField('noise_floor')

    @property
    def rssi(self):
        return self.pb.rssi if self.pb.HasField('rssi') else None

    @rssi.setter
    def rssi(self, value):
        if value is not None:
            self.pb.rssi = value
        else:
            self.pb.ClearField('rssi')

    @property
    def snr(self):
        return self.pb.snr if self.pb.HasField('snr') else None

    @snr.setter
    def snr(self, value):
        if value is not None:
            self.pb.snr = value
        else:
            self.pb.ClearField('snr')

    @property
    def rx_link_rate(self):
        return self.pb.rx_link_rate if self.pb.HasField('rx_link_rate') else None

    @rx_link_rate.setter
    def rx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.rx_link_rate = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.WirelessLink.rx_link_rate',
                    value=value,
                    metric_name='NetworkElement.WirelessLink.rx_link_rate')
        else:
            self.pb.ClearField('rx_link_rate')

    @property
    def tx_link_rate(self):
        return self.pb.tx_link_rate if self.pb.HasField('tx_link_rate') else None

    @tx_link_rate.setter
    def tx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.tx_link_rate = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.WirelessLink.tx_link_rate',
                    value=value,
                    metric_name='NetworkElement.WirelessLink.tx_link_rate')
        else:
            self.pb.ClearField('tx_link_rate')

    @property
    def rx_mcs(self):
        return self.pb.rx_mcs if self.pb.HasField('rx_mcs') else None

    @rx_mcs.setter
    def rx_mcs(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.rx_mcs = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.WirelessLink.rx_mcs',
                                value=value,
                                metric_name=' NetworkElement.WirelessLink.rx_mcs')
        else:
            self.pb.ClearField('rx_mcs')

    @property
    def tx_mcs(self):
        return self.pb.tx_mcs if self.pb.HasField('tx_mcs') else None

    @tx_mcs.setter
    def tx_mcs(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.tx_mcs = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.WirelessLink.tx_mcs',
                                value=value,
                                metric_name='NetworkElement.WirelessLink.tx_mcs')
        else:
            self.pb.ClearField('tx_mcs')

    @property
    def rx_modulation_rate(self):
        return self.pb.rx_modulation_rate if self.pb.HasField(
            'rx_modulation_rate') else None

    @rx_modulation_rate.setter
    def rx_modulation_rate(self, value):
        if value is not None:
            self.pb.rx_modulation_rate = value
        else:
            self.pb.ClearField('rx_modulation_rate')

    @property
    def tx_modulation_rate(self):
        return self.pb.tx_modulation_rate if self.pb.HasField(
            'tx_modulation_rate') else None

    @tx_modulation_rate.setter
    def tx_modulation_rate(self, value):
        if value is not None:
            self.pb.tx_modulation_rate = value
        else:
            self.pb.ClearField('tx_modulation_rate')

    @property
    def rx_airtime(self):
        return self.pb.rx_airtime if self.pb.HasField('rx_airtime') else None

    @rx_airtime.setter
    def rx_airtime(self, value):
        if value is not None:
            if value >= 0:
                self.pb.rx_airtime = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for WirelessLink.rx_airtime',
                                value=value,
                                metric_name='WirelessLink.rx_airtime')
        else:
            self.pb.ClearField('rx_airtime')

    @property
    def tx_airtime(self):
        return self.pb.tx_airtime if self.pb.HasField('tx_airtime') else None

    @tx_airtime.setter
    def tx_airtime(self, value):
        if value is not None:
            if value >= 0:
                self.pb.tx_airtime = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for WirelessLink.tx_airtime',
                                value=value,
                                metric_name='WirelessLink.tx_airtime')
        else:
            self.pb.ClearField('tx_airtime')

    @property
    def amc(self):
        return self.pb.amc if self.pb.HasField('amc') else None

    @amc.setter
    def amc(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.amc = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for WirelessLink.amc',
                                value=value,
                                metric_name='WirelessLink.amc')
        else:
            self.pb.ClearField('amc')

    @property
    def amq(self):
        return self.pb.amq if self.pb.HasField('amq') else None

    @amq.setter
    def amq(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.amq = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for WirelessLink.amq',
                                value=value,
                                metric_name='WirelessLink.amq')
        else:
            self.pb.ClearField('amq')

    @property
    def ccq(self):
        return self.pb.ccq if self.pb.HasField('ccq') else None

    @ccq.setter
    def ccq(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.ccq = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for WirelessLink.ccq',
                                value=value,
                                metric_name='WirelessLink.ccq')
        else:
            self.pb.ClearField('ccq')

    @property
    def poller_hash(self):
        return self.pb.poller_hash if self.pb.HasField('poller_hash') else None

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value


@pb_wrapper(network_poller_pb2.NetworkElement.RadioStream)
class RadioStream(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.links = Repeated(WirelessLink, self.pb.links, self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def id(self):
        return self.pb.id

    @id.setter
    def id(self, value):
        if value is not None:
            self.pb.id = value

    @property
    def polarization(self):
        return self.pb.polarization if self.pb.HasField('polarization') else None

    @polarization.setter
    def polarization(self, value):
        if value is not None:
            self.pb.polarization = value
        else:
            self.pb.ClearField('polarization')

    @property
    def noise_floor(self):
        return self.pb.noise_floor if self.pb.HasField('noise_floor') else None

    @noise_floor.setter
    def noise_floor(self, value):
        if value is not None:
            self.pb.noise_floor = value
        else:
            self.pb.ClearField('noise_floor')


@pb_wrapper(network_poller_pb2.NetworkElement.RfAggregationGroup)
class RfAggregationGroup(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def name(self):
        return self.pb.name if self.pb.HasField('name') else None

    @name.setter
    def name(self, value):
        if value:
            self.pb.name = value
        else:
            self.pb.ClearField('name')

    @property
    def airtime(self):
        return self.pb.airtime if self.pb.HasField('airtime') else None

    @airtime.setter
    def airtime(self, value):
        if value is not None:
            if value >= 0:
                self.pb.airtime = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.RfAggregationGroup.airtime',
                    value=value,
                    metric_name='NetworkElement.RfAggregationGroup.airtime')
        else:
            self.pb.ClearField('airtime')

    @property
    def tx_packet_error_rate(self):
        return self.pb.tx_packet_error_rate if self.pb.HasField(
            'tx_packet_error_rate') else None

    @tx_packet_error_rate.setter
    def tx_packet_error_rate(self, value):
        if value is not None:
            if value >= 0:
                self.pb.tx_packet_error_rate = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.RfAggregationGroup.tx_packet_error_rate',
                    value=value,
                    metric_name='NetworkElement.RfAggregationGroup.tx_packet_error_rate'
                )
        else:
            self.pb.ClearField('tx_packet_error_rate')

    @property
    def rx_packet_error_rate(self):
        return self.pb.rx_packet_error_rate if self.pb.HasField(
            'rx_packet_error_rate') else None

    @rx_packet_error_rate.setter
    def rx_packet_error_rate(self, value):
        if value is not None:
            if value >= 0:
                self.pb.rx_packet_error_rate = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.RfAggregationGroup.rx_packet_error_rate',
                    value=value,
                    metric_name='NetworkElement.RfAggregationGroup.rx_packet_error_rate'
                )
        else:
            self.pb.ClearField('rx_packet_error_rate')

    @property
    def tx_interference_signal_ratio(self):
        return self.pb.tx_interference_signal_ratio if self.pb.HasField(
            'tx_interference_signal_ratio') else None

    @tx_interference_signal_ratio.setter
    def tx_interference_signal_ratio(self, value):
        if value is not None:
            self.pb.tx_interference_signal_ratio = value
        else:
            self.pb.ClearField('tx_interference_signal_ratio')

    @property
    def rx_interference_signal_ratio(self):
        return self.pb.rx_interference_signal_ratio if self.pb.HasField(
            'rx_interference_signal_ratio') else None

    @rx_interference_signal_ratio.setter
    def rx_interference_signal_ratio(self, value):
        if value is not None:
            self.pb.rx_interference_signal_ratio = value
        else:
            self.pb.ClearField('rx_interference_signal_ratio')


@pb_wrapper(network_poller_pb2.NetworkElement.Radio)
class Radio(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.streams = Repeated(RadioStream, self.pb.streams, self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def id(self):
        return self.pb.id

    @id.setter
    def id(self, value):
        if value is not None:
            self.pb.id = value

    @property
    def frequency(self):
        return self.pb.frequency if self.pb.HasField('frequency') else None

    @frequency.setter
    def frequency(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.frequency = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.frequency',
                                value=value,
                                metric_name='NetworkElement.Radio.frequency')
        else:
            self.pb.ClearField('frequency')

    @property
    def chan_width(self):
        return self.pb.chan_width if self.pb.HasField('chan_width') else None

    @chan_width.setter
    def chan_width(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.chan_width = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.chan_width',
                                value=value,
                                metric_name='NetworkElement.Radio.chan_width')
        else:
            self.pb.ClearField('chan_width')

    @property
    def tx_power(self):
        return self.pb.tx_power if self.pb.HasField('tx_power') else None

    @tx_power.setter
    def tx_power(self, value):
        if value is not None:
            if int.bit_length(value) < 32:
                self.pb.tx_power = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.tx_power',
                                value=value,
                                metric_name='NetworkElement.Radio.tx_power')
        else:
            self.pb.ClearField('tx_power')

    @property
    def dl_percent(self):
        return self.pb.dl_percent if self.pb.HasField('dl_percent') else None

    @dl_percent.setter
    def dl_percent(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.dl_percent = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.dl_percent',
                                value=value,
                                metric_name='NetworkElement.Radio.dl_percent')
        else:
            self.pb.ClearField('dl_percent')

    @property
    def framing(self):
        return self.pb.framing

    @framing.setter
    def framing(self, value):
        if value is not None:
            self.pb.framing = value

    @property
    def rx_airtime(self):
        return self.pb.rx_airtime if self.pb.HasField('rx_airtime') else None

    @rx_airtime.setter
    def rx_airtime(self, value):
        if value is not None:
            if value >= 0:
                self.pb.rx_airtime = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.rx_airtime',
                                value=value,
                                metric_name='NetworkElement.Radio.rx_airtime')
        else:
            self.pb.ClearField('rx_airtime')

    @property
    def tx_airtime(self):
        return self.pb.tx_airtime if self.pb.HasField('tx_airtime') else None

    @tx_airtime.setter
    def tx_airtime(self, value):
        if value is not None:
            if value >= 0:
                self.pb.tx_airtime = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.tx_airtime',
                                value=value,
                                metric_name='NetworkElement.Radio.tx_airtime')
        else:
            self.pb.ClearField('tx_airtime')

    @property
    def gps_sync(self):
        return self.pb.gps_sync if self.pb.HasField('gps_sync') else None

    @gps_sync.setter
    def gps_sync(self, value):
        if value is not None:
            self.pb.gps_sync = value
        else:
            self.pb.ClearField('gps_sync')

    @property
    def chain_balance(self):
        return self.pb.chain_balance if self.pb.HasField('chain_balance') else None

    @chain_balance.setter
    def chain_balance(self, value):
        if value is not None:
            self.pb.chain_balance = value
        else:
            self.pb.ClearField('chain_balance')

    @property
    def rx_link_rate(self):
        return self.pb.rx_link_rate if self.pb.HasField('rx_link_rate') else None

    @rx_link_rate.setter
    def rx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.rx_link_rate = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.rx_link_rate',
                                value=value,
                                metric_name='NetworkElement.Radio.rx_link_rate')
        else:
            self.pb.ClearField('rx_link_rate')

    @property
    def tx_link_rate(self):
        return self.pb.tx_link_rate if self.pb.HasField('tx_link_rate') else None

    @tx_link_rate.setter
    def tx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.tx_link_rate = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.tx_link_rate',
                                value=value,
                                metric_name='NetworkElement.Radio.tx_link_rate')
        else:
            self.pb.ClearField('tx_link_rate')

    @property
    def frame_duration(self):
        return self.pb.frame_duration if self.pb.HasField('frame_duration') else None

    @frame_duration.setter
    def frame_duration(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.frame_duration = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.frame_duration',
                                value=value,
                                metric_name='NetworkElement.Radio.frame_duration')
        else:
            self.pb.ClearField('frame_duration')

    @property
    def rx_multiplexing_gain(self):
        return self.pb.rx_multiplexing_gain if self.pb.HasField(
            'rx_multiplexing_gain') else None

    @rx_multiplexing_gain.setter
    def rx_multiplexing_gain(self, value):
        if value is not None:
            self.pb.rx_multiplexing_gain = value
        else:
            self.pb.ClearField('rx_multiplexing_gain')

    @property
    def tx_multiplexing_gain(self):
        return self.pb.tx_multiplexing_gain if self.pb.HasField(
            'tx_multiplexing_gain') else None

    @tx_multiplexing_gain.setter
    def tx_multiplexing_gain(self, value):
        if value is not None:
            self.pb.tx_multiplexing_gain = value
        else:
            self.pb.ClearField('tx_multiplexing_gain')

    @property
    def rx_average_group_size(self):
        return self.pb.rx_average_group_size if self.pb.HasField(
            'rx_average_group_size') else None

    @rx_average_group_size.setter
    def rx_average_group_size(self, value):
        if value is not None:
            self.pb.rx_average_group_size = value
        else:
            self.pb.ClearField('rx_average_group_size')

    @property
    def tx_average_group_size(self):
        return self.pb.tx_average_group_size if self.pb.HasField(
            'tx_average_group_size') else None

    @tx_average_group_size.setter
    def tx_average_group_size(self, value):
        if value is not None:
            self.pb.tx_average_group_size = value
        else:
            self.pb.ClearField('tx_average_group_size')

    @property
    def rx_frutl(self):
        return self.pb.rx_frutl if self.pb.HasField('rx_frutl') else None

    @rx_frutl.setter
    def rx_frutl(self, value):
        if value is not None:
            if value >= 0:
                self.pb.rx_frutl = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.rx_frutl',
                                value=value,
                                metric_name='NetworkElement.Radio.rx_frutl')
        else:
            self.pb.ClearField('rx_frutl')

    @property
    def tx_frutl(self):
        return self.pb.tx_frutl if self.pb.HasField('tx_frutl') else None

    @tx_frutl.setter
    def tx_frutl(self, value):
        if value is not None:
            if value >= 0:
                self.pb.tx_frutl = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.tx_frutl',
                                value=value,
                                metric_name='NetworkElement.Radio.tx_frutl')
        else:
            self.pb.ClearField('tx_frutl')

    @property
    def wifi_mode(self):
        return self.pb.wifi_mode if self.pb.HasField('wifi_mode') else None

    @wifi_mode.setter
    def wifi_mode(self, value):
        if value is not None:
            if value > 0:
                self.pb.wifi_mode = value
            else:  # not a valid enum value
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                "Invalid value for Networkelement.Radio.wifi_mode",
                                value=value,
                                metric_name='NetworkElement.Radio.wifi_mode')
                self.pb.ClearField('wifi_mode')
        else:
            self.pb.ClearField('wifi_mode')

    @property
    def max_cpe_radios(self):
        return self.pb.max_cpe_radios if self.pb.HasField('max_cpe_radios') else None

    @max_cpe_radios.setter
    def max_cpe_radios(self, value):
        if value is None:
            self.pb.ClearField('max_cpe_radios')
        elif value >= 0 and int.bit_length(value) < 32:
            self.pb.max_cpe_radios = value
        elif self.errctx:
            self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                            'Invalid value for NetworkElement.Radio.max_cpe_radios',
                            value=value,
                            metric_name='NetworkElement.Radio.max_cpe_radios')

    @property
    def max_distance(self):
        return self.pb.max_distance if self.pb.HasField('max_distance') else None

    @max_distance.setter
    def max_distance(self, value):
        if value is None:
            self.pb.ClearField('max_distance')
        elif value >= 0 and int.bit_length(value) < 32:
            self.pb.max_distance = value
        elif self.errctx:
            self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                            'Invalid value for NetworkElement.Radio.max_distance',
                            value=value,
                            metric_name='NetworkElement.Radio.max_distance')

    @property
    def rx_path_loss(self):
        return self.pb.rx_path_loss if self.pb.HasField('rx_path_loss') else None

    @rx_path_loss.setter
    def rx_path_loss(self, value):
        if value is not None:
            if value >= -150 and value <= 150:
                self.pb.rx_path_loss = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Radio.rx_path_loss',
                                value=value,
                                metric_name='NetworkElement.Radio.rx_path_loss')
        else:
            self.pb.ClearField('rx_path_loss')

    @property
    def rx_max_interference_noise_ratio(self):
        return self.pb.rx_max_interference_noise_ratio if self.pb.HasField(
            'rx_max_interference_noise_ratio') else None

    @rx_max_interference_noise_ratio.setter
    def rx_max_interference_noise_ratio(self, value):
        if value is not None:
            if value >= -150 and value <= 150:
                self.pb.rx_max_interference_noise_ratio = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Radio.rx_max_interference_noise_ratio',
                    value=value,
                    metric_name='NetworkElement.Radio.rx_max_interference_noise_ratio')
        else:
            self.pb.ClearField('rx_max_interference_noise_ratio')

    @property
    def rx_max_sensitivity_loss(self):
        return self.pb.rx_max_sensitivity_loss if self.pb.HasField(
            'rx_max_sensitivity_loss') else None

    @rx_max_sensitivity_loss.setter
    def rx_max_sensitivity_loss(self, value):
        if value is not None:
            if value >= -150 and value <= 150:
                self.pb.rx_max_sensitivity_loss = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Radio.rx_max_sensitivity_loss',
                    value=value,
                    metric_name='NetworkElement.Radio.rx_max_sensitivity_loss')
        else:
            self.pb.ClearField('rx_max_sensitivity_loss')

    @property
    def azimuth(self):
        return self.pb.azimuth if self.pb.HasField('azimuth') else None

    @azimuth.setter
    def azimuth(self, value):
        if value is None:
            self.pb.ClearField('azimuth')
        elif value >= 0 and value <= 360:
            self.pb.azimuth = value
        elif self.errctx:
            self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                            'Invalid value for NetworkElement.Radio.azimuth',
                            value=value,
                            metric_name='NetworkElement.Radio.azimuth')

    @property
    def admin_up(self):
        return self.pb.admin_up if self.pb.HasField('admin_up') else None

    @admin_up.setter
    def admin_up(self, value):
        if value is not None:
            self.pb.admin_up = value
        else:
            self.pb.ClearField('admin_up')

    @property
    def oper_up(self):
        return self.pb.oper_up if self.pb.HasField('oper_up') else None

    @oper_up.setter
    def oper_up(self, value):
        if value is not None:
            self.pb.oper_up = value
        else:
            self.pb.ClearField('oper_up')


@pb_wrapper(network_poller_pb2.NetworkElement.Sector)
class Sector(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def ssid(self):
        return self.pb.ssid

    @ssid.setter
    def ssid(self, value):
        if value is not None:
            self.pb.ssid = value

    @property
    def mode(self):
        return self.pb.mode

    @mode.setter
    def mode(self, value):
        if value is not None:
            self.pb.mode = value


@pb_wrapper(network_poller_pb2.Error)
class Error(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)


@pb_wrapper(network_poller_pb2.NetworkElement.PonLink)
class PonLink(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def poller_hash(self):
        return self.pb.poller_hash

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value

    @property
    def rx_power(self):
        return self.pb.rx_power if self.pb.HasField('rx_power') else None

    @rx_power.setter
    def rx_power(self, value):
        if value is not None:
            if value > -40 and value < 40:
                self.pb.rx_power = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.PonLink.rx_power',
                                value=value,
                                metric_name='NetworkElement.PonLink.rx_power')
        else:
            self.pb.ClearField('rx_power')

    @property
    def tx_power(self):
        return self.pb.tx_power if self.pb.HasField('tx_power') else None

    @tx_power.setter
    def tx_power(self, value):
        if value is not None:
            if value >= -50 and value <= 50:
                self.pb.tx_power = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.PonLink.tx_power',
                                value=value,
                                metric_name='NetworkElement.PonLink.tx_power')
        else:
            self.pb.ClearField('tx_power')

    @property
    def fiber_length(self):
        return self.pb.fiber_length if self.pb.HasField('fiber_length') else None

    @fiber_length.setter
    def fiber_length(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.fiber_length = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.PonLink.fiber_length',
                                value=value,
                                metric_name='NetworkElement.PonLink.fiber_length')
        else:
            self.pb.ClearField('fiber_length')

    @property
    def connected_time(self):
        return self.pb.connected_time if self.pb.HasField('connected_time') else None

    @connected_time.setter
    def connected_time(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.connected_time = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.PonLink.connected_time',
                    value=value,
                    metric_name='NetworkElement.PonLink.connected_time')
        else:
            self.pb.ClearField('connected_time')

    @property
    def rx_link_rate(self):
        return self.pb.rx_link_rate if self.pb.HasField('rx_link_rate') else None

    @rx_link_rate.setter
    def rx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.rx_link_rate = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.PonLink.rx_link_rate',
                                value=value,
                                metric_name='NetworkElement.PonLink.rx_link_rate')
        else:
            self.pb.ClearField('rx_link_rate')

    @property
    def tx_link_rate(self):
        return self.pb.tx_link_rate if self.pb.HasField('tx_link_rate') else None

    @tx_link_rate.setter
    def tx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.tx_link_rate = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.PonLink.tx_link_rate',
                                value=value,
                                metric_name='NetworkElement.PonLink.tx_link_rate')
        else:
            self.pb.ClearField('tx_link_rate')


@pb_wrapper(network_poller_pb2.NetworkElement.Pon)
class Pon(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.links = Repeated(PonLink, self.pb.links, self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def id(self):
        return self.pb.id

    @id.setter
    def id(self, value):
        if value is not None:
            self.pb.id = value

    @property
    def poller_hash(self):
        return self.pb.poller_hash

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value

    @property
    def pon_technology(self):
        return self.pb.pon_technology if self.pb.HasField('pon_technology') else None

    @pon_technology.setter
    def pon_technology(self, value):
        if value is not None:
            if value > 0:
                self.pb.pon_technology = value
            else:  # not a valid enum value
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                "Invalid value for Networkelement.Pon.pon_technology",
                                value=value,
                                metric_name='NetworkElement.Pon.pon_technology')
                self.pb.ClearField('pon_technology')
        else:
            self.pb.ClearField('pon_technology')

    @property
    def split_ratio(self):
        return self.pb.split_ratio if self.pb.HasField('split_ratio') else None

    @split_ratio.setter
    def split_ratio(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.split_ratio = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Pon.split_ratio',
                                value=value,
                                metric_name='NetworkElement.Pon.split_ratio')
        else:
            self.pb.ClearField('split_ratio')

    @property
    def max_distance(self):
        return self.pb.max_distance if self.pb.HasField('max_distance') else None

    @max_distance.setter
    def max_distance(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.max_distance = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Pon.max_distance',
                                value=value,
                                metric_name='NetworkElement.Pon.max_distance')
        else:
            self.pb.ClearField('max_distance')

    @property
    def minimum_loss(self):
        return self.pb.minimum_loss if self.pb.HasField('minimum_loss') else None

    @minimum_loss.setter
    def minimum_loss(self, value):
        if value is not None:
            self.pb.minimum_loss = value
        else:
            self.pb.ClearField('minimum_loss')

    @property
    def maximum_loss(self):
        return self.pb.maximum_loss if self.pb.HasField('maximum_loss') else None

    @maximum_loss.setter
    def maximum_loss(self, value):
        if value is not None:
            self.pb.maximum_loss = value
        else:
            self.pb.ClearField('maximum_loss')

    @property
    def rx_link_rate(self):
        return self.pb.rx_link_rate if self.pb.HasField('rx_link_rate') else None

    @rx_link_rate.setter
    def rx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.rx_link_rate = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Pon.rx_link_rate',
                                value=value,
                                metric_name='NetworkElement.Pon.rx_link_rate')
        else:
            self.pb.ClearField('rx_link_rate')

    @property
    def tx_link_rate(self):
        return self.pb.tx_link_rate if self.pb.HasField('tx_link_rate') else None

    @tx_link_rate.setter
    def tx_link_rate(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.tx_link_rate = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Pon.tx_link_rate',
                                value=value,
                                metric_name='NetworkElement.Pon.tx_link_rate')
        else:
            self.pb.ClearField('tx_link_rate')

    @property
    def name(self):
        return self.pb.name if self.pb.HasField('name') else None

    @name.setter
    def name(self, value):
        if value is not None:
            self.pb.name = value
        else:
            self.pb.ClearField('name')

    @property
    def description(self):
        return self.pb.description if self.pb.HasField('description') else None

    @description.setter
    def description(self, value):
        if value is not None:
            self.pb.description = value
        else:
            self.pb.ClearField('description')


@pb_wrapper(network_poller_pb2.NetworkElement.Interface)
class Interface(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.ip_addresses = Repeated(None, self.pb.ip_addresses, self.errctx)
        self.bridged_mac_addresses = Repeated(None, self.pb.bridged_mac_addresses,
                                              self.errctx)
        self.links = Repeated(Link, self.pb.links, self.errctx)
        self.radios = Repeated(Radio, self.pb.radios, self.errctx)
        self.sectors = Repeated(Sector, self.pb.sectors, self.errctx)
        self.pons = Repeated(Pon, self.pb.pons, self.errctx)
        self.rf_aggregation_groups = Repeated(RfAggregationGroup,
                                              self.pb.rf_aggregation_groups,
                                              self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def id(self):
        return self.pb.id

    @id.setter
    def id(self, value):
        if value is not None:
            self.pb.id = value

    @property
    def name(self):
        return self.pb.name if self.pb.HasField('name') else None

    @name.setter
    def name(self, value):
        if value is not None:
            self.pb.name = value
        else:
            self.pb.ClearField('name')

    @property
    def type(self):
        return self.pb.type if self.pb.HasField('type') else None

    @type.setter
    def type(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.type = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.type',
                                value=value,
                                metric_name='NetworkElement.Interface.type')
        else:
            self.pb.ClearField('type')

    @property
    def mtu(self):
        return self.pb.mtu if self.pb.HasField('mtu') else None

    @mtu.setter
    def mtu(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.mtu = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.mtu',
                                value=value,
                                metric_name='NetworkElement.Interface.mtu')
        else:
            self.pb.ClearField('mtu')

    @property
    def speed(self):
        return self.pb.speed if self.pb.HasField('speed') else None

    @speed.setter
    def speed(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.speed = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.speed',
                                value=value,
                                metric_name='NetworkElement.Interface.speed')
        else:
            self.pb.ClearField('speed')

    @property
    def mac_address(self):
        return self.pb.mac_address if self.pb.HasField('mac_address') else None

    @mac_address.setter
    def mac_address(self, value):
        if value is not None:
            self.pb.mac_address = value
        else:
            self.pb.ClearField('mac_address')

    @property
    def admin_up(self):
        return self.pb.admin_up if self.pb.HasField('admin_up') else None

    @admin_up.setter
    def admin_up(self, value):
        if value is not None:
            self.pb.admin_up = value
        else:
            self.pb.ClearField('admin_up')

    @property
    def oper_up(self):
        return self.pb.oper_up if self.pb.HasField('oper_up') else None

    @oper_up.setter
    def oper_up(self, value):
        if value is not None:
            self.pb.oper_up = value
        else:
            self.pb.ClearField('oper_up')

    @property
    def in_octets(self):
        return self.pb.in_octets if self.pb.HasField('in_octets') else None

    @in_octets.setter
    def in_octets(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_octets = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.in_octets',
                                value=value,
                                metric_name='NetworkElement.Interface.in_octets')
        else:
            self.pb.ClearField('in_octets')

    @property
    def in_ucast_pkts(self):
        return self.pb.in_ucast_pkts if self.pb.HasField('in_ucast_pkts') else None

    @in_ucast_pkts.setter
    def in_ucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_ucast_pkts = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Interface.in_ucast_pkts',
                    value=value,
                    metric_name='NetworkElement.Interface.in_ucast_pkts')
        else:
            self.pb.ClearField('in_ucast_pkts')

    @property
    def in_nucast_pkts(self):
        return self.pb.in_nucast_pkts if self.pb.HasField('in_nucast_pkts') else None

    @in_nucast_pkts.setter
    def in_nucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_nucast_pkts = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Interface.in_nucast_pkts',
                    value=value,
                    metric_name='NetworkElement.Interface.in_nucast_pkts')
        else:
            self.pb.ClearField('in_nucast_pkts')

    @property
    def in_discards(self):
        return self.pb.in_discards if self.pb.HasField('in_discards') else None

    @in_discards.setter
    def in_discards(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_discards = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Interface.in_discards',
                    value=value,
                    metric_name='NetworkElement.Interface.in_discards')
        else:
            self.pb.ClearField('in_discards')

    @property
    def in_errors(self):
        return self.pb.in_errors if self.pb.HasField('in_errors') else None

    @in_errors.setter
    def in_errors(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_errors = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.in_errors',
                                value=value,
                                metric_name='NetworkElement.Interface.in_errors')
        else:
            self.pb.ClearField('in_errors')

    @property
    def in_unknown_protos(self):
        return self.pb.in_unknown_protos if self.pb.HasField(
            'in_unknown_protos') else None

    @in_unknown_protos.setter
    def in_unknown_protos(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.in_unknown_protos = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Interface.in_unknown_protos',
                    value=value,
                    metric_name='NetworkElement.Interface.in_unknown_protos')
        else:
            self.pb.ClearField('in_unknown_protos')

    @property
    def out_octets(self):
        return self.pb.out_octets if self.pb.HasField('out_octets') else None

    @out_octets.setter
    def out_octets(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_octets = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.out_octets',
                                value=value,
                                metric_name='NetworkElement.Interface.out_octets')
        else:
            self.pb.ClearField('out_octets')

    @property
    def out_ucast_pkts(self):
        return self.pb.out_ucast_pkts if self.pb.HasField('out_ucast_pkts') else None

    @out_ucast_pkts.setter
    def out_ucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_ucast_pkts = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Interface.out_ucast_pkts',
                    value=value,
                    metric_name='NetworkElement.Interface.out_ucast_pkts')
        else:
            self.pb.ClearField('out_ucast_pkts')

    @property
    def out_nucast_pkts(self):
        return self.pb.out_nucast_pkts if self.pb.HasField('out_nucast_pkts') else None

    @out_nucast_pkts.setter
    def out_nucast_pkts(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_nucast_pkts = value
            elif self.errctx:
                self.errctx.add(
                    network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                    'Invalid value for NetworkElement.Interface.out_nucast_pkts',
                    value=value,
                    metric_name='NetworkElement.Interface.out_nucast_pkts')
        else:
            self.pb.ClearField('out_nucast_pkts')

    @property
    def out_discards(self):
        return self.pb.out_discards if self.pb.HasField('out_discards') else None

    @out_discards.setter
    def out_discards(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_discards = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.discards',
                                value=value,
                                metric_name='NetworkElement.Interface.discards')
        else:
            self.pb.ClearField('out_discards')

    @property
    def out_errors(self):
        return self.pb.out_errors if self.pb.HasField('out_errors') else None

    @out_errors.setter
    def out_errors(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.out_errors = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Interface.out_errors',
                                value=value,
                                metric_name='NetworkElement.Interface.out_errors')
        else:
            self.pb.ClearField('out_errors')

    @property
    def poller_hash(self):
        return self.pb.poller_hash if self.pb.HasField('poller_hash') else None

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value
        else:
            self.pb.ClearField('poller_hash')

    @property
    def manufacturer(self):
        return self.pb.manufacturer if self.pb.HasField('manufacturer') else None

    @manufacturer.setter
    def manufacturer(self, value):
        if value is not None:
            self.pb.manufacturer = value
        else:
            self.pb.ClearField('manufacturer')

    @property
    def model(self):
        return self.pb.model if self.pb.HasField('model') else None

    @model.setter
    def model(self, value):
        if value is not None:
            self.pb.model = value
        else:
            self.pb.ClearField('model')

    def add_bridged_mac(self, mac):
        if mac.startswith('33:33:') or mac == '00:00:00:00:00:00':
            return  # ignore IPv6 multicast MACs (PPA-1321) and zero MACs
        self.bridged_mac_addresses.append(mac)


@pb_wrapper(network_poller_pb2.IpMac)
class IpMac(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def ip_address(self):
        return self.pb.ip_address

    @ip_address.setter
    def ip_address(self, value):
        if value is not None:
            self.pb.ip_address = value

    @property
    def mac_address(self):
        return self.pb.mac_address

    @mac_address.setter
    def mac_address(self, value):
        if value is not None:
            self.pb.mac_address = value

    @property
    def port(self):
        return self.pb.port if self.pb.HasField('port') else None

    @port.setter
    def port(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.port = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElementUpdate.IpMac.port',
                                value=value,
                                metric_name='NetworkElementUpdate.IpMac.port')
        else:
            self.pb.ClearField('port')


@pb_wrapper(network_poller_pb2.Route)
class Route(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def net(self):
        return self.pb.net

    @net.setter
    def net(self, value):
        if value is not None:
            self.pb.net = value

    @property
    def if_index(self):
        return self.pb.if_index if self.pb.HasField('if_index') else None

    @if_index.setter
    def if_index(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.if_index = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElementUpdate.Route.if_index',
                                value=value,
                                metric_name='NetworkElementUpdate.Route.if_index')
        else:
            self.pb.ClearField('if_index')

    @property
    def next_hop(self):
        return self.pb.next_hop if self.pb.HasField('next_hop') else None

    @next_hop.setter
    def next_hop(self, value):
        if value is not None:
            self.pb.next_hop = value
        else:
            self.pb.ClearField('next_hop')

    @property
    def mac_address(self):
        return self.pb.mac_address if self.pb.HasField('mac_address') else None

    @mac_address.setter
    def mac_address(self, value):
        if value is not None:
            self.pb.mac_address = value
        else:
            self.pb.ClearField('mac_address')


@pb_wrapper(network_poller_pb2.DhcpLease)
class DhcpLease(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def ip_address(self):
        return self.pb.ip_address

    @ip_address.setter
    def ip_address(self, value):
        if value is not None:
            self.pb.ip_address = value

    @property
    def mac_address(self):
        return self.pb.mac_address

    @mac_address.setter
    def mac_address(self, value):
        if value is not None:
            self.pb.mac_address = value

    @property
    def circuit_id(self):
        return self.pb.circuit_id if self.pb.HasField('circuit_id') else None

    @circuit_id.setter
    def circuit_id(self, value):
        if value is not None:
            self.pb.circuit_id = value
        else:
            self.pb.ClearField('circuit_id')

    @property
    def remote_id(self):
        return self.pb.remote_id if self.pb.HasField('remote_id') else None

    @remote_id.setter
    def remote_id(self, value):
        if value is not None:
            self.pb.remote_id = value
        else:
            self.pb.ClearField('remote_id')

    @property
    def hostname(self):
        return self.pb.hostname if self.pb.HasField('hostname') else None

    @hostname.setter
    def hostname(self, value):
        if value is not None:
            self.pb.hostname = value
        else:
            self.pb.ClearField('hostname')


@pb_wrapper(network_poller_pb2.PppoeClient)
class PppoeClient(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.network_prefixes = Repeated(None, self.pb.network_prefixes, self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def username(self):
        return self.pb.username

    @username.setter
    def username(self, value):
        if value is not None:
            self.pb.username = value

    @property
    def mac_address(self):
        return self.pb.mac_address if self.pb.HasField('mac_address') else None

    @mac_address.setter
    def mac_address(self, value):
        if value is not None:
            self.pb.mac_address = value
        else:
            self.pb.ClearField('mac_address')


@pb_wrapper(network_poller_pb2.NetworkElement.Module)
class Module(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.interfaces = Repeated(Interface, self.pb.interfaces, self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def id(self):
        return self.pb.id

    @id.setter
    def id(self, value):
        if value is not None:
            self.pb.id = value

    @property
    def poller_hash(self):
        return self.pb.poller_hash

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value

    @property
    def name(self):
        return self.pb.name if self.pb.HasField('name') else None

    @name.setter
    def name(self, value):
        if value is not None:
            self.pb.name = value
        else:
            self.pb.ClearField('name')

    @property
    def manufacturer(self):
        return self.pb.manufacturer if self.pb.HasField('manufacturer') else None

    @manufacturer.setter
    def manufacturer(self, value):
        if value is not None:
            self.pb.manufacturer = value
        else:
            self.pb.ClearField('manufacturer')

    @property
    def model(self):
        return self.pb.model if self.pb.HasField('model') else None

    @model.setter
    def model(self, value):
        if value is not None:
            self.pb.model = value
        else:
            self.pb.ClearField('model')

    @property
    def serial_number(self):
        return self.pb.serial_number if self.pb.HasField('serial_number') else None

    @serial_number.setter
    def serial_number(self, value):
        if value is not None:
            self.pb.serial_number = value
        else:
            self.pb.ClearField('serial_number')

    @property
    def sw_version(self):
        return self.pb.sw_version if self.pb.HasField('sw_version') else None

    @sw_version.setter
    def sw_version(self, value):
        if value is not None:
            self.pb.sw_version = value
        else:
            self.pb.ClearField('sw_version')

    @property
    def uptime(self):
        return self.pb.uptime if self.pb.HasField('uptime') else None

    @uptime.setter
    def uptime(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.uptime = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Module.uptime',
                                value=value,
                                metric_name='NetworkElement.Module.uptime')
        else:
            self.pb.ClearField('uptime')

    @property
    def cpu(self):
        return self.pb.cpu if self.pb.HasField('cpu') else None

    @cpu.setter
    def cpu(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.cpu = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Module.cpu',
                                value=value,
                                metric_name='NetworkElement.Module.cpu')
        else:
            self.pb.ClearField('cpu')

    @property
    def mem(self):
        return self.pb.mem if self.pb.HasField('mem') else None

    @mem.setter
    def mem(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.mem = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.Module.mem',
                                value=value,
                                metric_name='NetworkElement.Module.mem')
        else:
            self.pb.ClearField('mem')

    @property
    def temperature(self):
        return self.pb.temperature if self.pb.HasField('temperature') else None

    @temperature.setter
    def temperature(self, value):
        if value is not None:
            self.pb.temperature = value
        else:
            self.pb.ClearField('temperature')


@pb_wrapper(network_poller_pb2.NetworkElement)
class NetworkElement(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        self.modules = Repeated(Module, self.pb.modules, self.errctx)
        self.interfaces = Repeated(Interface, self.pb.interfaces, self.errctx)
        self.errors = Repeated(Error, self.pb.errors, self.errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)

    @property
    def management_ip(self):
        return self.pb.management_ip if self.pb.HasField('management_ip') else None

    @management_ip.setter
    def management_ip(self, value):
        if value is not None:
            self.pb.management_ip = value
        else:
            self.pb.ClearField('management_ip')

    @property
    def name(self):
        return self.pb.name if self.pb.HasField('name') else None

    @name.setter
    def name(self, value):
        if value is not None:
            self.pb.name = value
        else:
            self.pb.ClearField('name')

    @property
    def manufacturer(self):
        return self.pb.manufacturer if self.pb.HasField('manufacturer') else None

    @manufacturer.setter
    def manufacturer(self, value):
        if value is not None:
            self.pb.manufacturer = value
        else:
            self.pb.ClearField('manufacturer')

    @property
    def model(self):
        return self.pb.model if self.pb.HasField('model') else None

    @model.setter
    def model(self, value):
        if value is not None:
            self.pb.model = value
        else:
            self.pb.ClearField('model')

    @property
    def serial_number(self):
        return self.pb.serial_number if self.pb.HasField('serial_number') else None

    @serial_number.setter
    def serial_number(self, value):
        if value is not None:
            self.pb.serial_number = value
        else:
            self.pb.ClearField('serial_number')

    @property
    def system_mac_address(self):
        return self.pb.system_mac_address if self.pb.HasField(
            'system_mac_address') else None

    @system_mac_address.setter
    def system_mac_address(self, value):
        if value is not None:
            if int(value.replace(':', ''), 16) < 2**48:
                self.pb.system_mac_address = value
            else:
                raise ValueError(f'Invalid MAC address "{value}"')
        else:
            self.pb.ClearField('system_mac_address')

    @property
    def sw_version(self):
        return self.pb.sw_version if self.pb.HasField('sw_version') else None

    @sw_version.setter
    def sw_version(self, value):
        if value is not None:
            self.pb.sw_version = value
        else:
            self.pb.ClearField('sw_version')

    @property
    def uptime(self):
        return self.pb.uptime if self.pb.HasField('uptime') else None

    @uptime.setter
    def uptime(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 64:
                self.pb.uptime = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.uptime',
                                value=value,
                                metric_name='NetworkElement.uptime')
        else:
            self.pb.ClearField('uptime')

    @property
    def cpu(self):
        return self.pb.cpu if self.pb.HasField('cpu') else None

    @cpu.setter
    def cpu(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.cpu = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.cpu',
                                value=value,
                                metric_name='NetworkElement.cpu')
        else:
            self.pb.ClearField('cpu')

    @property
    def mem(self):
        return self.pb.mem if self.pb.HasField('mem') else None

    @mem.setter
    def mem(self, value):
        if value is not None:
            if value >= 0 and value <= 100:
                self.pb.mem = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.mem',
                                value=value,
                                metric_name='NetworkElement.mem')
        else:
            self.pb.ClearField('mem')

    @property
    def temperature(self):
        return self.pb.temperature if self.pb.HasField('temperature') else None

    @temperature.setter
    def temperature(self, value):
        if value is not None:
            self.pb.temperature = value
        else:
            self.pb.ClearField('temperature')

    @property
    def poller_hash(self):
        return self.pb.poller_hash

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value

    @property
    def gps_sats_tracked(self):
        return self.pb.gps_sats_tracked if self.pb.HasField(
            'gps_sats_tracked') else None

    @gps_sats_tracked.setter
    def gps_sats_tracked(self, value):
        if value is not None:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.gps_sats_tracked = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElement.gps_sats_tracked',
                                value=value,
                                metric_name='NetworkElement.gps_sats_tracked')
        else:
            self.pb.ClearField('gps_sats_tracked')

    @property
    def latitude(self):
        return self.pb.latitude if self.pb.HasField('latitude') else None

    @latitude.setter
    def latitude(self, value):
        if value is not None:
            self.pb.latitude = value
        else:
            self.pb.ClearField('latitude')

    @property
    def longitude(self):
        return self.pb.longitude if self.pb.HasField('longitude') else None

    @longitude.setter
    def longitude(self, value):
        if value is not None:
            self.pb.longitude = value
        else:
            self.pb.ClearField('longitude')

    @property
    def elevation(self):
        return self.pb.elevation if self.pb.HasField('elevation') else None

    @elevation.setter
    def elevation(self, value):
        if value is not None:
            self.pb.elevation = value
        else:
            self.pb.ClearField('elevation')

    @property
    def height(self):
        return self.pb.height if self.pb.HasField('height') else None

    @height.setter
    def height(self, value):
        if value is not None:
            self.pb.height = value
        else:
            self.pb.ClearField('height')

    @property
    def status(self):
        return self.pb.status

    @status.setter
    def status(self, value):
        if value is not None:
            self.pb.status = value

    @property
    def imsi(self):
        return self.pb.imsi if self.pb.HasField('imsi') else None

    @imsi.setter
    def imsi(self, value):
        if value is None:
            self.pb.ClearField('imsi')
        else:
            self.pb.imsi = value

    @property
    def admin_disabled(self):
        return self.pb.admin_disabled if self.pb.HasField('admin_disabled') else None

    @admin_disabled.setter
    def admin_disabled(self, value):
        if value is None:
            self.pb.ClearField('admin_disabled')
        else:
            self.pb.admin_disabled = value

    @property
    def extra(self):
        return self.pb.extra if self.pb.HasField('extra') else None

    @extra.setter
    def extra(self, value):
        if value is not None:
            self.pb.extra = value
        else:
            self.pb.ClearField('extra')

    @property
    def counter_source(self):
        return self.pb.counter_source if self.pb.HasField('counter_source') else None

    @counter_source.setter
    def counter_source(self, value):
        if value is not None:
            self.pb.counter_source = value
        else:
            self.pb.ClearField('counter_source')

    @property
    def offline_reason(self):
        return self.pb.offline_reason if self.pb.HasField('offline_reason') else None

    @offline_reason.setter
    def offline_reason(self, value):
        if value is not None:
            if value > 0:
                self.pb.offline_reason = value
            else:  # not a valid enum value
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                "Invalid value for Networkelement.offline_reason",
                                value=value,
                                metric_name='NetworkElement.offline_reason')
                self.pb.ClearField('offline_reason')
        else:
            self.pb.ClearField('offline_reason')

    @property
    def offline_reason_detail(self):
        return self.pb.offline_reason_detail if self.pb.HasField(
            'offline_reason_detail') else None

    @offline_reason_detail.setter
    def offline_reason_detail(self, value):
        if value is not None:
            self.pb.offline_reason_detail = value
        else:
            self.pb.ClearField('offline_reason_detail')

    @property
    def offline_time(self):
        return self.pb.offline_time.ToDatetime(
            timezone.utc) if self.pb.HasField('offline_time') else None

    @offline_time.setter
    def offline_time(self, value):
        if value is not None:
            val = self.pb.offline_time.FromDatetime(value)
        else:
            self.pb.ClearField('offline_time')


@pb_wrapper(network_poller_pb2.NetworkElementUpdate)
class NetworkElementUpdate(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)
        self.errors = Repeated(None, self.pb.errors, self.errctx)
        self.peers = Repeated(NetworkElement, self.pb.peers, self.errctx)
        self.ip_macs = Repeated(IpMac, self.pb.ip_macs, self.errctx)
        self.routes = Repeated(Route, self.pb.routes, self.errctx)
        self.dhcp_leases = Repeated(DhcpLease, self.pb.dhcp_leases, self.errctx)
        self.pppoe_clients = Repeated(PppoeClient, self.pb.pppoe_clients, self.errctx)

    @property
    def time(self):
        return self.pb.time.seconds

    @time.setter
    def time(self, value):  # assumes seconds
        if value is not None:
            self.pb.time.FromSeconds(int(value))

    @property
    def company_uuid(self):
        return self.pb.company_uuid

    @company_uuid.setter
    def company_uuid(self, value):
        if value is not None:
            self.pb.company_uuid = value

    @property
    def element_uuid(self):
        return self.pb.element_uuid

    @element_uuid.setter
    def element_uuid(self, value):
        if value is not None:
            self.pb.element_uuid = value

    @property
    def instance(self):
        return self.pb.instance

    @instance.setter
    def instance(self, value):
        if value is not None:
            self.pb.instance = value

    @property
    def poller_version(self):
        return self.pb.poller_version

    @poller_version.setter
    def poller_version(self, value):
        if value is not None:
            self.pb.poller_version = value

    @property
    def active(self):
        return self.pb.active

    @active.setter
    def active(self, value):
        if value is not None:
            self.pb.active = value

    @property
    def pingable(self):
        return self.pb.pingable

    @pingable.setter
    def pingable(self, value):
        if value is not None:
            self.pb.pingable = value

    @property
    def ping_time(self):
        return self.pb.ping_time if self.pb.HasField('ping_time') else None

    @ping_time.setter
    def ping_time(self, value):
        if value is None:
            self.pb.ClearField('ping_time')
        else:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.ping_time = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElementUpdate.ping_time',
                                value=value,
                                metric_name='NetworkElementUpdate.ping_time')

    @property
    def snmp_version(self):
        return self.pb.snmp_version

    @snmp_version.setter
    def snmp_version(self, value):
        if value is not None:
            self.pb.snmp_version = value

    @property
    def sysobjid(self):
        return self.pb.sysobjid if self.pb.HasField('sysobjid') else None

    @sysobjid.setter
    def sysobjid(self, value):
        if value is None:
            self.pb.ClearField('sysobjid')
        else:
            self.pb.sysobjid = value

    @property
    def data(self):
        return NetworkElement(self.pb.data)

    @data.setter
    def data(self, value):
        if value is not None:
            self.pb.data.CopyFrom(value.pb)

    @property
    def poll_time(self):
        return self.pb.poll_time if self.pb.HasField('poll_time') else None

    @poll_time.setter
    def poll_time(self, value):
        if value is None:
            self.pb.ClearField('poll_time')
        elif value >= 0 and int.bit_length(value) < 32:
            self.pb.poll_time = value
        elif self.errctx:
            self.errctx.add(network_poller_pb2.PollError.ERROR_BAD_DATA_RANGE,
                            'Invalid value for NetworkElementUpdate.poll_time',
                            value=value,
                            metric_name='NetworkElementUpdate.poll_time')

    @property
    def is_topology_stale(self):
        return self.pb.is_topology_stale if self.pb.HasField(
            'is_topology_stale') else None

    @is_topology_stale.setter
    def is_topology_stale(self, value):
        if value is not None:
            self.pb.is_topology_stale = value
        else:
            self.pb.ClearField('is_topology_stale')


@pb_wrapper(network_poller_pb2.NetworkElementDiscovery)
class NetworkElementDiscovery(BaseWrapperClass):

    def __init__(self, pb=None, errctx=None, **kwargs):
        super().__init__(pb, errctx)
        for key, value in kwargs.items():
            setattr(self, key, value)
        self.errors = Repeated(None, self.pb.errors, self.errctx)

    @property
    def time(self):
        return self.pb.time.seconds

    @time.setter
    def time(self, value):  # assumes seconds
        if value is not None:
            self.pb.time.FromSeconds(int(value))

    @property
    def company_uuid(self):
        return self.pb.company_uuid

    @company_uuid.setter
    def company_uuid(self, value):
        if value is not None:
            self.pb.company_uuid = value

    @property
    def element_discovery_uuid(self):
        return self.pb.element_discovery_uuid

    @element_discovery_uuid.setter
    def element_discovery_uuid(self, value):
        if value is not None:
            self.pb.element_discovery_uuid = value

    @property
    def instance(self):
        return self.pb.instance

    @instance.setter
    def instance(self, value):
        if value is not None:
            self.pb.instance = value

    @property
    def active(self):
        return self.pb.active

    @active.setter
    def active(self, value):
        if value is not None:
            self.pb.active = value

    @property
    def pingable(self):
        return self.pb.pingable

    @pingable.setter
    def pingable(self, value):
        if value is not None:
            self.pb.pingable = value

    @property
    def ping_time(self):
        return self.pb.ping_time if self.pb.HasField('ping_time') else None

    @ping_time.setter
    def ping_time(self, value):
        if value is None:
            self.pb.ClearField('ping_time')
        else:
            if value >= 0 and int.bit_length(value) < 32:
                self.pb.ping_time = value
            elif self.errctx:
                self.errctx.add(network_poller_pb2.Error.ERROR_BAD_DATA_RANGE,
                                'Invalid value for NetworkElementDiscovery.ping_time',
                                value=value,
                                metric_name='NetworkElementDiscovery.ping_time')

    @property
    def snmp_version(self):
        return self.pb.snmp_version

    @snmp_version.setter
    def snmp_version(self, value):
        if value is not None:
            self.pb.snmp_version = value

    @property
    def sysobjid(self):
        return self.pb.sysobjid if self.pb.HasField('sysobjid') else None

    @sysobjid.setter
    def sysobjid(self, value):
        if value is None:
            self.pb.ClearField('sysobjid')
        else:
            self.pb.sysobjid = value

    @property
    def poller_version(self):
        return self.pb.poller_version

    @poller_version.setter
    def poller_version(self, value):
        if value is None:
            self.pb.ClearField('poller_version')
        else:
            self.pb.poller_version = value

    @property
    def poller_hash(self):
        return self.pb.poller_hash

    @poller_hash.setter
    def poller_hash(self, value):
        if value is not None:
            self.pb.poller_hash = value

    @property
    def status(self):
        return self.pb.status

    @status.setter
    def status(self, value):
        if value is not None:
            self.pb.status = value
