#!/usr/bin/perl

use warnings;
use strict;

use Getopt::Std;
use DateTime;
use JSON;
use Digest::SHA1 qw/sha1_hex/;
use MIME::Base64;
use Config::Simple;
use Data::Dumper;
use Compress::Zlib;
use CIF::FeedTables;

my %opts;
getopts('hDc:d:f:s:r:m:F:',\%opts);

my $feed        = $opts{'f'} || shift || '';
my $severity    = $opts{'s'} || 'high';
my $restriction = $opts{'r'};
my $maxdays     = $opts{'d'} || 30;
my $debug       = $opts{'D'};
my $config      = $opts{'c'} || $ENV{'HOME'}.'/.cif';
my $last_feeds  = $opts{'F'} || 5;

my $cfg = Config::Simple->new($config) || die('missing config file');
my $maxrecords  = $opts{'m'} || $cfg->param(-block => 'cif_feeds')->{'maxrecords'} || 50000;

unless($feed ne '') {
    if(my $x = $cfg->param(-block => 'cif_feeds')->{'feeds'}){
        $feed = join(',',@{$x});
    } else {
        # auto generate the feeds from the warehouse tables

        my $t = CIF::FeedTables->new();
        my @x = map { $_->relname() } $t->search__feed_tables();
        my @y;
        foreach (@x){ $_ =~ s/feed_//; $_ =~ s/\_/\//; }
        $feed = join(',',@x);
    }
}

my @feeds = split(/,/,$feed);

my $restriction_map = $cfg->param(-block => 'restrictions');
unless($restriction){
    if(my $x = $restriction_map->{'default'}){
        $restriction = $x;
    } else {
        $restriction = 'private';
    }
}

die usage() if($opts{'h'} || $#feeds < 0);

sub usage {
    my $usage_feeds = join("\n\t",@feeds);
    return <<EOF;
Usage: perl $0 -s medium -f infrastructure/botnet
    -h  --help:         this meessage
    -c  --config:       specify config file, default: $config
    -f  --feed:         feed to pull, comma seperated, default:

        $usage_feeds

    -s  --severity:     feed severity (low|medium|high), default: $severity
    -r  --restriction:  feed restriction (default|public|need-to-know|private), default: $restriction
    -m  --maxrecords:   max feed records, default: $maxrecords
    -d  --maxdays:      max days to go back in feed search, default: $maxdays


Examples:
    \$> perl $0 -f infrastructure
    \$> perl $0 -f infrastructure/botnet -s low
    \$> perl $0 -f domain/malware -D

EOF
}

my %sev = (
    'high'      => 3,
    'medium'    => 2,
    'low'       => 1,
);
my $dt = DateTime->from_epoch(epoch => time());
$dt = $dt->ymd().'T'.$dt->hms().'Z';

foreach (@feeds){
    my $start = time();

    _debug('processing: '.$_);
    my @bits    = split(/\//,$_);
    my $impact  = ucfirst($bits[$#bits]);
    my $type    = ucfirst($bits[$#bits-1]);
    $impact     = '' unless($#bits);
    my $description = $type;
    $description = $impact.' '.($type) unless($impact eq '');
    $description .= ' feed';

    my $bucket = 'CIF::Message::'.$type.$impact;
    eval "require $bucket";
    die($@) if($@);
    my $feed_bucket = 'CIF::Message::Feed'.$type.$impact;
    eval "require $feed_bucket";
    die($@) if($@);

    my $detecttime = DateTime->from_epoch(epoch => (time() - (84600 * $maxdays)));
    my @recs;
    _profile('searching',$start);
    if(lc($impact) =~ /whitelist/){
        @recs = $bucket->retrieve_from_sql(qq{
            detecttime >= '$detecttime'
            ORDER BY id DESC
            LIMIT $maxrecords
        });
    } else {
        my @args = ($bucket =~ /Domain/) ? ($severity,$restriction,$maxrecords) : ($detecttime,$severity,$restriction,$maxrecords);
        @recs = $bucket->search_feed(@args);

    }
    _profile('generating',$start);
    @recs = $feed_bucket->generate(@recs);
    my @res;
    unless($#recs > -1){
        _debug('no records');
        next();
    }
    _debug('records: '.$#recs);
    _profile('sorting',$start);
    @recs = sort { $a->{'detecttime'} cmp $b->{'detecttime'} } @recs;
    _profile('mapping',$start);
    if(keys %$restriction_map){
        foreach my $r (@recs){
            if(exists($restriction_map->{$r->{'restriction'}})){
                $r->{'restriction'} = $restriction_map->{$r->{'restriction'}};
            }
            if(exists($restriction_map->{$r->{'alternativeid_restriction'}})){
                $r->{'alternativeid_restriction'} = $restriction_map->{$r->{'alternativeid_restriction'}};
           }
        }
    }
    $severity = 'low' if(lc($impact) eq 'search');
    if($impact eq ''){
        $impact = $type;
    } else {
        $impact = $type.' '.$impact;
    }
    my $hash = {
        restriction => $restriction_map->{lc($restriction)},
        impact      => lc($impact),
        severity    => lc($severity),
        items       => \@recs,
        detecttime  => $dt,
    };
    
    my $json = to_json($hash);
    my $zcontent = encode_base64(compress($json));
    my $id = $feed_bucket->insert({
        format      => 'application/json',
        source      => 'ren-isac.net',
        message     => $zcontent,
        hash_sha1   => sha1_hex($zcontent),
        severity    => $severity,
        description => lc($description),
        impact      => lc($impact),
        restriction => lc($restriction),
    });

    print lc($description).': '.$id.' created -- '.$id->uuid()."\n";
    my @feeds = $feed_bucket->retrieve_from_sql(qq{
        severity = '$severity'
        AND restriction = '$restriction'
        ORDER BY id ASC
    });
    if($#feeds >= $last_feeds){
        foreach (0 ... $last_feeds-1){
            pop(@feeds);
        }
        foreach (@feeds){
            print 'removing feed: '.$_->id().' -- '.$_->uuid()."\n";
            CIF::Message->retrieve(uuid => $_->uuid())->delete();
        }
    }
        
    ## TODO -- if $id is < than most recent feed (of this type), we need to re-insert this as a new feed (or delete the old feed)
    _profile('finished',$start);
}

sub _debug {
    return unless($debug);
    warn shift;
}

sub _profile {
    #my ($msg,$start) = @_;
    #_debug('('.(time() - $start).') '.$msg);
}
