#!/usr/bin/perl

=encoding UTF-8
=cut

=head1 DESCRIPTION
=cut

# common modules
use strict;
use warnings FATAL => 'all';
use feature 'say';
use utf8;
use open qw(:std :utf8);

use LWP::UserAgent;
use Net::INET6Glue::INET_is_INET6;
use Carp;
use JSON::XS;
use Getopt::Long;
use Term::ANSIColor;

use lib::abs '../lib';

use PiSecrets;

my $url_check = 'http://juggler-api.search.yandex.net:8998/api/checks/add_or_update?do=1';
my $url_send  = "http://juggler-push.search.yandex.net/events";

=head2 get_opts

Читает и парсит опции которые были переданы скрипту. Завершает работу скрипта если что-то
не так или если передана опция --help

    my $opts = get_opts();

Возвращает структуру с валидными значениями вида

    {
        host => 'partner_rtb.mon4dev',
        service => 'check_name',
        status => 'OK',
        ttl_seconds => 30,
    }

=cut

sub get_opts {

    my $host;
    my $service;
    my $status;
    my $ttl;
    my $only_send;
    my $only_create;
    my $verbose;
    my $namespace;
    my $dry_run;

    my $help;

    GetOptions(
        "namespace=s" => \$namespace,
        "host=s"      => \$host,
        "service=s"   => \$service,
        "status=s"    => \$status,
        "ttl=s"       => \$ttl,
        "only_send"   => \$only_send,
        "only_create" => \$only_create,
        "verbose"     => \$verbose,
        "dry_run"     => \$dry_run,
        "help|h"      => \$help,
    ) or die("Error in command line arguments\n");

    if ($help) {
        _output_help();
        exit 0;
    }

    unless ($namespace && $host && $service && ($status or $only_create) && ($ttl or $only_send)) {
        say colored('FATAL ERROR', 'red');
        say "Must specify options --namespace, --host, --service, --status or --only_create, --ttl or --only_send";
        exit 1;
    }

    if (length($service) > 128) {
        say colored('FATAL ERROR', 'red');
        say "Option --service must be <= 128 symbols";
        exit 1;
    }

    unless ($only_send) {
        if ($ttl !~ /^[0-9]+[smh]\z/) {
            say colored('FATAL ERROR', 'red');
            say "Option --ttl must be something like 10s, 5m or 24h";
            exit 1;
        }
    }

    unless ($only_create) {
        my %status = map {$_ => 1} qw(OK INFO WARN CRIT);

        if (!exists $status{$status}) {
            say colored('FATAL ERROR', 'red');
            say "Option --status must be one of " . join ', ', keys %status;
            exit 1;
        }
    }

    my $parsed_opts = {
        namespace => $namespace,
        host      => $host,
        service   => $service,
        ($only_create ? () : (status      => $status)),
        ($only_send   ? () : (ttl_seconds => _ttl_to_seconds($ttl))),
        only_send   => $only_send,
        only_create => $only_create,
        verbose     => $verbose,
        dry_run     => $dry_run,
    };
    return $parsed_opts;
}

=head2 create_check

Создает проверку в juggler. Метод работает через API ручку

    create_check(
        host => 'partner_rtb.mon4dev',
        service => 'check_name',
        ttl_seconds => 86400,
        namespace => 'partner.test',
    );

Можно проверить что получилось с помощью команды вида

    jctl config check --host partner_rtb.mon4dev --service check_name

=cut

sub create_check {
    my (%opts) = @_;

    my $body = encode_json(
        {
            namespace => $opts{namespace},
            host      => $opts{host},
            service   => $opts{service},
            ttl       => $opts{ttl_seconds},
        }
    );
    warn 'POST ' . $url_check . ' ' . $body . "\n" if $opts{verbose} or $opts{dry_run};
    return if $opts{dry_run};

    my $ua = LWP::UserAgent->new;
    my $req = HTTP::Request->new(POST => $url_check);
    $req->header('content-type'  => 'application/json');
    $req->header('authorization' => 'OAuth ' . get_secret('juggler-token'));
    $req->content($body);
    my $response = $ua->request($req);

    my $parsed_content;
    if ($response->is_success) {
        warn $response->decoded_content . "\n" if $opts{verbose};
        eval {$parsed_content = decode_json $response->decoded_content};
    }

    if (!$parsed_content->{success}) {

        say colored('FATAL ERROR', 'red');
        say colored('Can\'t create check in juggler');

        say '-' x 78 . "\n";

        say 'Request:';
        say "url:      $url_check";
        say 'method:   POST';
        say "body:     $body";

        say "\n" . '-' x 78 . "\n";

        say 'Response:';
        say "status:   " . ($response->status_line || "** empty **");
        say "body:     " . ($response->content     || "** empty **");

        say "\n" . '-' x 78;

        exit 1;

    }
}

=head2 send_event

Отправляет событие в juggler. Метод работает через API ручку

    send_event(
        host => 'partner_rtb.mon4dev',
        service => 'check_name',
        status => 'OK', # 'INFO', 'WARN', 'CRIT'
    );

=cut

sub send_event {
    my (%opts) = @_;

    my $host = $opts{host} || `hostname -f`;
    chomp($host);

    my $body = encode_json(
        {
            source => $opts{namespace},
            events => [
                {
                    host        => $host,
                    service     => $opts{service},
                    status      => $opts{status},
                    description => 'event'
                }
            ]
        }
    );

    warn 'POST ' . $url_send . ' ' . $body . "\n" if $opts{verbose} or $opts{dry_run};
    return if $opts{dry_run};

    my $ua = LWP::UserAgent->new;
    my $req = HTTP::Request->new(POST => $url_send);
    $req->header('content-type' => 'application/json');
    $req->content($body);
    my $response = $ua->request($req);

    my $parsed_content;
    if ($response->is_success) {
        warn $response->decoded_content . "\n" if $opts{verbose};
        eval {$parsed_content = decode_json $response->decoded_content};
    }

    if (!$parsed_content->{success}) {

        say colored('FATAL ERROR', 'red');
        say colored('Can\'t send event to juggler');

        say '-' x 78 . "\n";

        say 'Request:';
        say "url:      $url_send";
        say 'method:   POST';
        say "body:     $body";

        say "\n" . '-' x 78 . "\n";

        say 'Response:';
        say "status:   " . $response->code;
        say "body:     " . $response->decoded_content;

        say "\n" . '-' x 78;

        exit 1;

    }
}

sub _output_help {

    say '';
    say ' ' x 4 . $0 . ' --host=partner_rtb.mon4dev --service=check_name --status=OK --ttl=24h';
    say '';
    say
'Скрипт создает (или изменяет проверку в juggler) и отправляет туда указанный статус.';
    say 'Ходит в http API juggler';
    say '';
    say 'Обязательные параметры:';
    say '';
    say ' * --host - название хоста проверки в juggler';
    say ' * --service - название сервиса в juggler';
    say
' * --status - статус в который нужно перевести проверку. Может быть OK, WARN, CRIT';
    say
' * --ttl - время через сколько времени проверка перейдет в CRIT после последней отправки данных (10s, 5m, 24h)';
    say '';
    say 'Необязательные параметры:';
    say '';
    say ' * --only_send - только отправить статус';
    say ' * --only_create - только создать проверку';
    say ' * --verbose - писать что делается';
    say ' * --dry_run - ничего не делать, только написать запросы';
}

sub _ttl_to_seconds {
    my ($ttl) = @_;

    my ($number, $postfix) = $ttl =~ /^([0-9]+)(.)\z/;

    my %h = (
        s => 1,
        m => 60,
        h => 60 * 60,
    );

    return $number * $h{$postfix};
}

# main
sub main {

    my $opts = get_opts();

    unless ($opts->{only_send}) {
        create_check(
            namespace   => $opts->{namespace},
            host        => $opts->{host},
            service     => $opts->{service},
            ttl_seconds => $opts->{ttl_seconds},
            verbose     => $opts->{verbose},
            dry_run     => $opts->{dry_run},
        );
    }

    unless ($opts->{only_create}) {
        send_event(
            namespace => $opts->{namespace},
            host      => $opts->{host},
            service   => $opts->{service},
            status    => $opts->{status},
            verbose   => $opts->{verbose},
            dry_run   => $opts->{dry_run},
        );
    }

}
main();
__END__
