package App::Netdisco::Worker::Plugin::Discover::VLANs;

use Dancer ':syntax';
use Dancer::Plugin::DBIC 'schema';

use App::Netdisco::Worker::Plugin;
use App::Netdisco::Transport::SNMP ();

use aliased 'App::Netdisco::Worker::Status';
use List::MoreUtils 'uniq';

register_worker({ phase => 'main', driver => 'snmp' }, sub {
  my ($job, $workerconf) = @_;

  my $device = $job->device;
  return unless $device->in_storage;
  my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
    or return Status->defer("discover failed: could not SNMP connect to $device");

  my $v_name  = $snmp->v_name;
  my $v_index = $snmp->v_index;

  # cache the device ports to save hitting the database for many single rows
  my $device_ports = vars->{'device_ports'}
    || { map {($_->port => $_)} $device->ports->all };

  my $i_vlan      = $snmp->i_vlan;
  my $i_vlan_type = $snmp->i_vlan_type;
  my $interfaces  = $snmp->interfaces;
  my $i_vlan_membership          = $snmp->i_vlan_membership;
  my $i_vlan_membership_untagged = $snmp->i_vlan_membership_untagged;

  my %p_seen = ();
  my @portvlans = ();
  my @active_ports = uniq (keys %$i_vlan_membership_untagged, keys %$i_vlan_membership);

  # build port vlans suitable for DBIC
  foreach my $entry (@active_ports) {
      my $port = $interfaces->{$entry} or next;

      if (!defined $device_ports->{$port}) {
          debug sprintf ' [%s] vlans - local port %s already skipped, ignoring',
            $device->ip, $port;
          next;
      }

      my %this_port_vlans = ();
      my $type = $i_vlan_type->{$entry};

      foreach my $vlan (@{ $i_vlan_membership_untagged->{$entry} || [] }) {
          next unless $vlan;
          next if $this_port_vlans{$vlan};
          my $native = ((defined $i_vlan->{$entry})
                          and ($vlan eq $i_vlan->{$entry})) ? 't' : 'f';

          push @portvlans, {
              port => $port,
              vlan => $vlan,
              native => $native,
              egress_tag => 'f',
              vlantype => $type,
              last_discover => \'LOCALTIMESTAMP',
          };

          ++$this_port_vlans{$vlan};
          ++$p_seen{$vlan};
      }

      foreach my $vlan (@{ $i_vlan_membership->{$entry} || [] }) {
          next unless $vlan;
          next if $this_port_vlans{$vlan};
          my $native = ((defined $i_vlan->{$entry})
                          and ($vlan eq $i_vlan->{$entry})) ? 't' : 'f';

          push @portvlans, {
              port => $port,
              vlan => $vlan,
              native => $native,
              egress_tag => ($native eq 't' ? 'f' : 't'),
              vlantype => $type,
              last_discover => \'LOCALTIMESTAMP',
          };

          ++$this_port_vlans{$vlan};
          ++$p_seen{$vlan};
      }
  }

  schema('netdisco')->txn_do(sub {
    my $gone = $device->port_vlans->delete;
    debug sprintf ' [%s] vlans - removed %d port VLANs',
      $device->ip, $gone;
    $device->port_vlans->populate(\@portvlans);

    debug sprintf ' [%s] vlans - added %d new port VLANs',
      $device->ip, scalar @portvlans;
  });

  my %d_seen = ();
  my @devicevlans = ();

  # add named vlans to the device
  foreach my $entry (keys %$v_name) {
      my $vlan = $v_index->{$entry};
      next unless $vlan;
      next unless defined $vlan and $vlan;
      ++$d_seen{$vlan};

      push @devicevlans, {
          vlan => $vlan,
          description => $v_name->{$entry},
          last_discover => \'LOCALTIMESTAMP',
      };
  }

  # also add unnamed vlans to the device
  foreach my $vlan (keys %p_seen) {
      next unless $vlan;
      next if $d_seen{$vlan};
      push @devicevlans, {
          vlan => $vlan,
          description => (sprintf "VLAN %d", $vlan),
          last_discover => \'LOCALTIMESTAMP',
      };
  }

  # support for Hooks
  vars->{'hook_data'}->{'vlans'} = \@devicevlans;

  schema('netdisco')->txn_do(sub {
    my $gone = $device->vlans->delete;
    debug sprintf ' [%s] vlans - removed %d device VLANs',
      $device->ip, $gone;
    $device->vlans->populate(\@devicevlans);

    debug sprintf ' [%s] vlans - added %d new device VLANs',
      $device->ip, scalar @devicevlans;
  });

  return Status->info(sprintf ' [%s] vlans - discovered for ports and device',
    $device->ip);
});

true;
