package App::Netdisco::DB::ResultSet::NodeIp;
use base 'App::Netdisco::DB::ResultSet';

use strict;
use warnings;

__PACKAGE__->load_components(qw/
  +App::Netdisco::DB::ExplicitLocking
/);

my $order_by_time_last_and_join_manufacturer = {
    order_by => {'-desc' => 'time_last'},
    '+columns' => [
      'manufacturer.company',
      'manufacturer.abbrev',
      { time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
      { time_last_stamp =>  \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
    ],
    join => 'manufacturer'
};

=head1 with_times

This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized columns to the result set:

=over 4

=item time_first_stamp

=item time_last_stamp

=back

=cut

sub with_times {
  my ($rs, $cond, $attrs) = @_;

  return $rs
    ->search_rs({}, $order_by_time_last_and_join_manufacturer)
    ->search($cond, $attrs);
}

=head1 with_router

This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized column to the result set:

=over 4

=item router_ip

=item router_name

=back

=cut

sub with_router {
  my ($rs, $cond, $attrs) = @_;

  return $rs
    ->search_rs({}, {
        '+columns' => [
          { router_ip =>  \q{(SELECT key FROM json_each_text(seen_on_router_last::json) ORDER BY value::timestamp DESC LIMIT 1)} },
          { router_name => \q{COALESCE(NULLIF(router.dns,''), NULLIF(router.name,''), '')} },
        ],
        join => 'router'
      })
    ->search($cond, $attrs);
}

=head1 search_by_ip( \%cond, \%attrs? )

 my $set = $rs->search_by_ip({ip => '192.0.2.1', active => 1});

Like C<search()>, this returns a ResultSet of matching rows from the
NodeIp table.

=over 4

=item *

The C<cond> parameter must be a hashref containing a key C<ip> with the value
to search for. Value can either be a simple string of IPv4 or IPv6, or a
L<NetAddr::IP::Lite> object in which case all results within the CIDR/Prefix
will be retrieved.

=item *

Results are ordered by time last seen.

=item *

Additional columns C<time_first_stamp> and C<time_last_stamp> provide
preformatted timestamps of the C<time_first> and C<time_last> fields.

=item *

A JOIN is performed on the Manufacturer table and the Manufacturer C<company> column prefetched.

=back

To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.

=cut

sub search_by_ip {
    my ($rs, $cond, $attrs) = @_;

    die "ip address required for search_by_ip\n"
      if ref {} ne ref $cond or !exists $cond->{ip};

    # handle either plain text IP or NetAddr::IP (/32 or CIDR)
    my ($op, $ip) = ('=', delete $cond->{ip});

    if ('NetAddr::IP::Lite' eq ref $ip and $ip->num > 1) {
        $op = '<<=';
        $ip = $ip->cidr;
    }
    $cond->{'me.ip'} = { $op => $ip };

    return $rs
      ->search_rs({}, $order_by_time_last_and_join_manufacturer)
      ->search($cond, $attrs);
}

=head1 search_by_dns( \%cond, \%attrs? )

 my $set = $rs->search_by_dns({
   dns => 'foo.example.com',
   suffix => qr/(?:\.example\..com|\.local)$/,
   active => 1
 });

Like C<search()>, this returns a ResultSet of matching rows from the
NodeIp table.

=over 4

=item *

The NodeIp table must have a C<dns> column for this search to work. Typically
this column is the IP's DNS PTR record, cached at the time of Netdisco Arpnip.

=item *

The C<cond> parameter must be a hashref containing a key C<dns> with the value
to search for. The value may optionally include SQL wildcard characters.

=item *

If C<dns> is a plain string, then the C<cond> parameter may optionally have a
C<suffix> parameter which is a regular expression of domain names - one of
which must match the results.

=item *

Results are ordered by time last seen.

=item *

Additional columns C<time_first_stamp> and C<time_last_stamp> provide
preformatted timestamps of the C<time_first> and C<time_last> fields.

=item *

A JOIN is performed on the Manufacturer table and the Manufacturer C<company> column prefetched.

=back

To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.

=cut

sub search_by_dns {
    my ($rs, $cond, $attrs) = @_;

    die "dns field required for search_by_dns\n"
      if ref {} ne ref $cond or !exists $cond->{dns};

    my $dns_field = delete $cond->{dns};

    (my $suffix = ($cond->{suffix} || ''))
      =~ s|\Q(?^\E[-xismu]*|(?|g;

    if (q{} eq ref $dns_field and exists $cond->{suffix}) {
        (my $stripped_dns_field = $dns_field) =~ s/\.\%$//;
        (my $fqdn_field = $stripped_dns_field) .= '%';
        $stripped_dns_field =~ s/$cond->{suffix}$// if $cond->{suffix};
        $stripped_dns_field .= '.%';

        $cond->{'me.dns'} = [ -or =>
          [ -and =>
              { '-ilike' => $stripped_dns_field },
              { '~*' => "***:$suffix" },
          ],
          [ -and =>
              { '-ilike' => $dns_field },
              { '~*' => "***:$suffix" },
          ],
          { '-ilike' => $fqdn_field },
        ];
    }
    elsif (q{} ne ref $dns_field) {
        $cond->{'me.dns'} = $dns_field;
    }
    else {
        $cond->{'me.dns'} = { '-ilike' => $dns_field };
    }

    delete $cond->{suffix};
    return $rs
      ->search_rs({}, $order_by_time_last_and_join_manufacturer)
      ->search($cond, $attrs);
}

=head1 search_by_mac( \%cond, \%attrs? )

 my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});

Like C<search()>, this returns a ResultSet of matching rows from the
NodeIp table.

=over 4

=item *

The C<cond> parameter must be a hashref containing a key C<mac> with the value
to search for.

=item *

Results are ordered by time last seen.

=item *

Additional columns C<time_first_stamp> and C<time_last_stamp> provide
preformatted timestamps of the C<time_first> and C<time_last> fields.

=item *

A JOIN is performed on the Manufacturer table and the Manufacturer C<company> column prefetched.

=back

To limit results only to active IPs, set C<< {active => 1} >> in C<cond>.

=cut

sub search_by_mac {
    my ($rs, $cond, $attrs) = @_;

    die "mac address required for search_by_mac\n"
      if ref {} ne ref $cond or !exists $cond->{mac};

    return $rs
      ->search_rs({}, $order_by_time_last_and_join_manufacturer)
      ->search($cond, $attrs);
}

=head2 ip_version( $version )

 my $rset = $rs->ip_version(4);

This predefined C<search()> returns a ResultSet of matching rows from the
NodeIp table of nodes with addresses of the supplied IP version.

=over 4

=item *

The C<version> parameter must be an integer either 4 or 6.

=back

=cut

sub ip_version {
    my ( $rs, $version ) = @_;

    die "ip_version input must be either 4 or 6\n"
        unless $version && ( $version == 4 || $version == 6 );

    return $rs->search_rs( \[ 'family(me.ip) = ?', $version ] );
}

1;
