#
# Copyright 2024 Centreon (http://www.centreon.com/)
#
# Centreon is a full-fledged industry-strength solution that meets
# the needs in IT infrastructure and application monitoring for
# service performance.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

package snmp_standard::mode::listinterfaces;

use base qw(centreon::plugins::mode);

use strict;
use warnings;
use Safe;
use snmp_standard::mode::resources::types qw($map_iftype);

my $oid_speed32 = '.1.3.6.1.2.1.2.2.1.5'; # in b/s
my $oid_speed64 = '.1.3.6.1.2.1.31.1.1.1.15';

sub set_oids_status {
    my ($self, %options) = @_;

    $self->{oid_adminstatus} = '.1.3.6.1.2.1.2.2.1.7';
    $self->{oid_adminstatus_mapping} = {
        0 => 'noStatus', 1 => 'up', 2 => 'down', 3 => 'testing', 4 => 'unknown', 5 => 'dormant', 6 => 'notPresent', 7 => 'lowerLayerDown'
    };
    $self->{oid_opstatus} = '.1.3.6.1.2.1.2.2.1.8';
    $self->{oid_opstatus_mapping} = {
        0 => 'noStatus', 1 => 'up', 2 => 'down', 3 => 'testing', 4 => 'unknown', 5 => 'dormant', 6 => 'notPresent', 7 => 'lowerLayerDown'
    };
    $self->{oid_mac_address} = '.1.3.6.1.2.1.2.2.1.6';
    $self->{oid_iftype} = '.1.3.6.1.2.1.2.2.1.3';
}

sub check_oids_label {
    my ($self, %options) = @_;

    foreach (('oid_filter', 'oid_display')) {
        $self->{option_results}->{$_} = lc($self->{option_results}->{$_}) if (defined($self->{option_results}->{$_}));
        if (!defined($self->{oids_label}->{$self->{option_results}->{$_}})) {
            my $label = $_;
            $label =~ s/_/-/g;
            $self->{output}->add_option_msg(short_msg => "Unsupported oid in --" . $label . " option.");
            $self->{output}->option_exit();
        }
    }
}

sub set_oids_label {
    my ($self, %options) = @_;

    $self->{oids_label} = {
        'ifdesc' => '.1.3.6.1.2.1.2.2.1.2',
        'ifalias' => '.1.3.6.1.2.1.31.1.1.1.18',
        'ifname' => '.1.3.6.1.2.1.31.1.1.1.1'
    };
}

sub default_oid_filter_name {
    my ($self, %options) = @_;

    return 'ifname';
}

sub default_oid_display_name {
    my ($self, %options) = @_;

    return 'ifname';
}

sub is_admin_status_down {
    my ($self, %options) = @_;

    if (defined($self->{option_results}->{use_adminstatus}) && defined($options{admin_status}) && 
        $self->{oid_adminstatus_mapping}->{$options{admin_status}} ne 'up') {
        return 1;
    }
    return 0;
}

sub new {
    my ($class, %options) = @_;
    my $self = $class->SUPER::new(package => defined($options{package}) ? $options{package} : __PACKAGE__, %options);
    bless $self, $class;

    $self->{no_speed} = defined($options{no_speed}) && $options{no_speed} =~ /^[01]$/ ? $options{no_speed} : 0;
    $options{options}->add_options(arguments => { 
        'name'                    => { name => 'use_name' },
        'interface:s'             => { name => 'interface' },
        'speed:s'                 => { name => 'speed' },
        'filter-status:s'         => { name => 'filter_status' },
        'skip-speed0'             => { name => 'skip_speed0' },
        'use-adminstatus'         => { name => 'use_adminstatus' },
        'oid-filter:s'            => { name => 'oid_filter', default => $self->default_oid_filter_name() },
        'oid-display:s'           => { name => 'oid_display', default => $self->default_oid_display_name() },
        'display-transform-src:s' => { name => 'display_transform_src' },
        'display-transform-dst:s' => { name => 'display_transform_dst' },
        'add-extra-oid:s@'        => { name => 'add_extra_oid' },
        'add-mac-address'         => { name => 'add_mac_address' }
    });

    $self->{interface_id_selected} = [];

    $self->{safe} = Safe->new();
    $self->{safe}->share('$assign_var');

    return $self;
}

sub check_options {
    my ($self, %options) = @_;
    $self->SUPER::init(%options);

    $self->set_oids_label();
    $self->check_oids_label();
    $self->set_oids_status();

    $self->{extra_oids} = {};
    foreach (@{$self->{option_results}->{add_extra_oid}}) {
        next if ($_ eq '');
        my ($name, $oid, $matching) = split /,/;
        $matching = '%{instance}$' if (!defined($matching));
        if (!defined($oid) || $oid !~ /^(\.\d+){1,}$/ || $name eq '') {
            $self->{output}->add_option_msg(short_msg => "Wrong syntax for add-extra-oid '" . $_ . "' option.");
            $self->{output}->option_exit();
        }
        $self->{extra_oids}->{$name} = { oid => $oid, matching => $matching };
    }
}

sub run {
    my ($self, %options) = @_;
    $self->{snmp} = $options{snmp};

    $self->manage_selection();
    my $result = $self->get_additional_information();
    foreach (sort @{$self->{interface_id_selected}}) {
        my $display_value = $self->get_display_value(id => $_);

        my $interface_speed = 0;
        if ($self->{no_speed} == 0) {
            $interface_speed = (defined($result->{$oid_speed64 . "." . $_}) && $result->{$oid_speed64 . "." . $_} ne '' && $result->{$oid_speed64 . "." . $_} != 0) ? 
                                ($result->{$oid_speed64 . "." . $_}) : 
                                    (defined($result->{$oid_speed32 . "." . $_}) && $result->{$oid_speed32 . "." . $_} ne '' && $result->{$oid_speed32 . "." . $_} != 0 ?
                                        (sprintf("%g", $result->{$oid_speed32 . "." . $_} / 1000 / 1000)) : '');
        }
        if (defined($self->{option_results}->{speed}) && $self->{option_results}->{speed} ne '') {
            $interface_speed = $self->{option_results}->{speed};
        }

        if (defined($self->{option_results}->{skip_speed0}) && ($interface_speed eq '' || $interface_speed == 0)) {
            $self->{output}->output_add(long_msg => "skipping interface '" . $display_value . "': interface speed is 0 and option --skip-speed0 is set");
            next;
        }
        if (defined($self->{option_results}->{filter_status}) && defined($result->{$self->{oid_opstatus} . "." . $_}) && 
            $self->{oid_opstatus_mapping}->{$result->{$self->{oid_opstatus} . "." . $_}} !~ /$self->{option_results}->{filter_status}/i) {
            $self->{output}->output_add(long_msg => "skipping interface '" . $display_value . "': no matching filter status");
            next;
        }
        if ($self->is_admin_status_down(admin_status => $result->{$self->{oid_adminstatus} . "." . $_})) {
            $self->{output}->output_add(long_msg => "skipping interface '" . $display_value . "': adminstatus is not 'up' and option --use-adminstatus is set");
            next;
        }

        my $extra_values = $self->get_extra_values_by_instance(result => $result, instance => $_);
        my $extra_display = '';
        foreach my $name (keys %{$extra_values}) {
            $extra_display .= '[' . $name . ' = ' . $extra_values->{$name} . ']';
        }
        if (defined($self->{oid_iftype})) {
            $extra_display .= sprintf(
                '[type = %s]',
                defined($result->{ $self->{oid_iftype} . '.' . $_}) && defined($map_iftype->{ $result->{ $self->{oid_iftype} . '.' . $_ } }) ?
                    $map_iftype->{ $result->{ $self->{oid_iftype} . '.' . $_ } } :
                    'unknown' 
            );
        }

        $self->{output}->output_add(
            long_msg => sprintf(
                "'%s' [speed = %s][status = %s][id = %s]%s",
                $display_value,
                $interface_speed,
                (defined($result->{$self->{oid_opstatus} . "." . $_}) ?  $self->{oid_opstatus_mapping}->{$result->{$self->{oid_opstatus} . "." . $_}} : ''),
                $_,
                $extra_display
            )
        );
    }

    $self->{output}->output_add(
        severity => 'OK',
        short_msg => 'List interfaces:'
    );
    $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1);
    $self->{output}->exit();
}

sub get_additional_information {
    my ($self, %options) = @_;

    my $oids = [];
    push @$oids, $self->{oid_adminstatus} if (defined($self->{oid_adminstatus}));
    push @$oids, $self->{oid_opstatus} if (defined($self->{oid_opstatus}));
    push @$oids, $self->{oid_mac_address} if (defined($self->{option_results}->{add_mac_address}));
    push @$oids, $self->{oid_iftype} if (defined($self->{oid_iftype}));
    push @$oids, $oid_speed32 if ($self->{no_speed} == 0);
    push @$oids, $oid_speed64 if (!$self->{snmp}->is_snmpv1() && $self->{no_speed} == 0);

    $self->{snmp}->load(oids => $oids, instances => $self->{interface_id_selected});
    return $self->{snmp}->get_leef();
}

sub get_display_value {
    my ($self, %options) = @_;

    our $assign_var = $self->{datas}->{$self->{option_results}->{oid_display} . '_' . $options{id}};
    if (defined($self->{option_results}->{display_transform_src})) {
        $self->{option_results}->{display_transform_dst} = '' if (!defined($self->{option_results}->{display_transform_dst}));

        $self->{safe}->reval("\$assign_var =~ s{$self->{option_results}->{display_transform_src}}{$self->{option_results}->{display_transform_dst}}", 1);
        if ($@) {
            die 'Unsafe code evaluation: ' . $@;
        }
    }

    return $assign_var;
}

sub manage_selection {
    my ($self, %options) = @_;

    my $oids = [{ oid => $self->{oids_label}->{$self->{option_results}->{oid_filter}} }];
    if ($self->{option_results}->{oid_filter} ne $self->{option_results}->{oid_display}) {
        push @$oids, { oid => $self->{oids_label}->{$self->{option_results}->{oid_display}} };
    }
    if (scalar(keys %{$self->{extra_oids}}) > 0) {
        foreach (keys %{$self->{extra_oids}}) {
            push @$oids, { oid => $self->{extra_oids}->{$_}->{oid} };
        }
    }

    $self->{datas} = {};
    $self->{results} = $self->{snmp}->get_multiple_table(oids => $oids);
    $self->{datas}->{all_ids} = [];
    foreach my $key ($self->{snmp}->oid_lex_sort(keys %{$self->{results}->{ $self->{oids_label}->{$self->{option_results}->{oid_filter}} }})) {
        next if ($key !~ /^$self->{oids_label}->{$self->{option_results}->{oid_filter}}\.(.*)$/);
        $self->{datas}->{$self->{option_results}->{oid_filter} . "_" . $1} = $self->{output}->decode($self->{results}->{$self->{oids_label}->{ $self->{option_results}->{oid_filter}} }->{$key});
        push @{$self->{datas}->{all_ids}}, $1;
    }

    if (scalar(@{$self->{datas}->{all_ids}}) <= 0) {
        $self->{output}->add_option_msg(short_msg => "Can't get interfaces...");
        $self->{output}->option_exit();
    }

    if ($self->{option_results}->{oid_filter} ne $self->{option_results}->{oid_display}) {
        foreach my $key ($self->{snmp}->oid_lex_sort(keys %{$self->{results}->{ $self->{oids_label}->{$self->{option_results}->{oid_display}} }})) {
            next if ($key !~ /^$self->{oids_label}->{$self->{option_results}->{oid_display}}\.(.*)$/);
            $self->{datas}->{$self->{option_results}->{oid_display} . "_" . $1} = $self->{output}->decode($self->{results}->{$self->{oids_label}->{ $self->{option_results}->{oid_display}} }->{$key});
        }
    }

    if (!defined($self->{option_results}->{use_name}) && defined($self->{option_results}->{interface})) {
        foreach (@{$self->{datas}->{all_ids}}) {
            if ($self->{option_results}->{interface} =~ /(^|\s|,)$_(\s*,|$)/) {
                push @{$self->{interface_id_selected}}, $_;
            }
        }
    } else {
        foreach (@{$self->{datas}->{all_ids}}) {
            my $filter_name = $self->{datas}->{$self->{option_results}->{oid_filter} . "_" . $_};
            next if (!defined($filter_name));

            if (!defined($self->{option_results}->{interface})) {
                push @{$self->{interface_id_selected}}, $_;
                next;
            }
            if ($filter_name =~ /$self->{option_results}->{interface}/) {
                push @{$self->{interface_id_selected}}, $_; 
            }
        }
    }

    if (scalar(@{$self->{interface_id_selected}}) <= 0 && !defined($options{disco})) {
        $self->{output}->add_option_msg(short_msg => 'No entry found');
        $self->{output}->option_exit();
    }
}

sub get_extra_values_by_instance {
    my ($self, %options) = @_;

    my $extra_values = {};
    foreach my $name (keys %{$self->{extra_oids}}) {
        my $matching = $self->{extra_oids}->{$name}->{matching};
        $matching =~ s/%\{instance\}/$options{instance}/g;
        next if (!defined($self->{results}->{ $self->{extra_oids}->{$name}->{oid} }));

        my $append = '';
        foreach (keys %{$self->{results}->{ $self->{extra_oids}->{$name}->{oid} }}) {
            if (/^$self->{extra_oids}->{$name}->{oid}\.$matching/) {
                $extra_values->{$name} = '' if (!defined($extra_values->{$name}));
                $extra_values->{$name} .= $append . $self->{output}->decode($self->{results}->{ $self->{extra_oids}->{$name}->{oid} }->{$_});
                $append = ',';
            }
        }
    }

    if (defined($self->{option_results}->{add_mac_address})) {
        my $macaddress = defined($options{result}->{$self->{oid_mac_address} . "." . $_}) ? unpack('H*', $options{result}->{$self->{oid_mac_address} . "." . $_}) : '';
        $macaddress =~ s/(..)(?=.)/$1:/g;
        $extra_values->{macaddress} = $macaddress;
    }

    return $extra_values;
}

sub disco_format {
    my ($self, %options) = @_;

    my $names = ['name', 'total', 'status', 'interfaceid'];
    if (scalar(keys %{$self->{extra_oids}}) > 0) {
        push @$names, keys %{$self->{extra_oids}};
    }
    if (defined($self->{option_results}->{add_mac_address})) {
        push @$names, 'macaddress';
    }
    push @$names, 'type' if (defined($self->{oid_iftype}));

    $self->{output}->add_disco_format(elements => $names);
}

sub disco_show {
    my ($self, %options) = @_;

    $self->{snmp} = $options{snmp};
    $self->manage_selection(disco => 1);
    return if (scalar(@{$self->{interface_id_selected}}) == 0);
    my $result = $self->get_additional_information();
    foreach (sort @{$self->{interface_id_selected}}) {
        my $display_value = $self->get_display_value(id => $_);

        my $interface_speed = 0;
        if ($self->{no_speed} == 0) {
            $interface_speed = (defined($result->{$oid_speed64 . "." . $_}) && $result->{$oid_speed64 . "." . $_} ne '' && $result->{$oid_speed64 . "." . $_} != 0) ? 
                                ($result->{$oid_speed64 . "." . $_}) : 
                                    (defined($result->{$oid_speed32 . "." . $_}) && $result->{$oid_speed32 . "." . $_} ne '' && $result->{$oid_speed32 . "." . $_} != 0 ?
                                        (sprintf("%g", $result->{$oid_speed32 . "." . $_} / 1000 / 1000)) : '');
        }
        if (defined($self->{option_results}->{speed}) && $self->{option_results}->{speed} ne '') {
            $interface_speed = $self->{option_results}->{speed};
        }
        next if (defined($self->{option_results}->{skip_speed0}) && $interface_speed == 0);
        next if (defined($self->{option_results}->{filter_status}) && defined($result->{$self->{oid_opstatus} . "." . $_}) && 
            $self->{oid_opstatus_mapping}->{$result->{$self->{oid_opstatus} . "." . $_}} !~ /$self->{option_results}->{filter_status}/i);
        next if ($self->is_admin_status_down(admin_status => $result->{$self->{oid_adminstatus} . "." . $_}));

        my $extra_values = $self->get_extra_values_by_instance(result => $result, instance => $_);
        if (defined($self->{oid_iftype})) {
            $extra_values->{type} = defined($result->{ $self->{oid_iftype} . '.' . $_ }) && defined($map_iftype->{ $result->{ $self->{oid_iftype} . '.' . $_ } }) ? 
                $map_iftype->{ $result->{ $self->{oid_iftype} . '.' . $_ } } : 'unknown';
        }

        $self->{output}->add_disco_entry(
            name => $display_value,
            total => $interface_speed,
            status => defined($result->{$self->{oid_opstatus} . "." . $_}) ? $self->{oid_opstatus_mapping}->{$result->{$self->{oid_opstatus} . "." . $_}} : '',
            interfaceid => $_,
            %$extra_values
        );
    }
}

1;

__END__

=head1 MODE

=over 8

=item B<--interface>

Define which interfaces to monitor (number expected). Example: 1,2... (empty means 'check all interfaces').

=item B<--name>

Allows you to define the interface (in option --interface) by name instead of OID index. The name matching mode supports regular expressions.

=item B<--speed>

Set interface speed (in Mb).

=item B<--skip-speed0>

Avoid displaying interfaces with bandwidth/speed equal to 0.

=item B<--filter-status>

Filter interfaces based on their status using a regular expression (example: 'up|UP').

=item B<--use-adminstatus>

Display interfaces with C<AdminStatus> 'up'.

=item B<--oid-filter>

Define the OID to be used to filter interfaces (default: C<ifName>).
Available OIDs: C<ifDesc>, C<ifAlias>, C<ifName>).

=item B<--oid-display>

Define the OID that will be used to name the interfaces (default: ifName).
Available OIDs: C<ifDesc>, C<ifAlias>, C<ifName>).

=item B<--display-transform-src> B<--display-transform-dst>

Modify the interface name displayed by using a regular expression.

Example: adding C<--display-transform-src='eth' --display-transform-dst='ens'>  will replace all occurrences of 'eth' with 'ens'

=item B<--add-extra-oid>

Display an OID.
Example: C<--add-extra-oid='alias,.1.3.6.1.2.1.31.1.1.1.18'>
or C<--add-extra-oid='vlan,.1.3.6.1.2.1.31.19,%{instance}\..*'>

=item B<--add-mac-address>

Display interfaces MAC addresses.

=back

=cut
