#!/usr/bin/perl -w

# Copyright (C) 2015 SUSE Linux GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.

=head1 dump_templates

dump_templates - dump openQA templates

=head1 SYNOPSIS

dump_templates [OPTIONS] [TABLES...]

=head1 OPTIONS

=over 4

=item B<--host> HOST

connect to specified host, defaults to localhost

=item B<--apibase>

Set API base URL component, default: '/api/v1'

=item B<--apikey> KEY, B<--apisecret> SECRET

Specify api key and secret to use, overrides use of config file ~/.config/openqa/client.conf

override values from config file

=item B<--group> GROUP

dump only specified job group

=item B<--test> NAME

dump only testsuite with specified name

=item B<--machine> NAME

dump only machine with specified name

=item B<--product> NAME

dump only product with specified name

=item B<--full>

when dumping a specific job group also dump related test suites, products and machines

=item B<--json>

dump as json

=item B<--help, -h>

print help

=back

=head1 TABLES

=over 4

=item B<Machines>

=item B<TestSuites>

=item B<Products>

=item B<JobTemplates>

=back

=head1 SYNOPSIS

=item dump_templates --host openqa.example.com Products

=item dump_templates --host openqa.example.com --group "openSUSE Tumbleweed" --full

=item dump_templates --host openqa.example.com --test "gnome"

=item dump_templates --host openqa.example.com --machine "64bit"

=head1 DESCRIPTION

Dump openQA database tables for Machines TestSuites Products
JobTemplates e.g. to load them on another instance.

=cut

use FindBin;
use lib "$FindBin::RealBin/../lib";

use strict;
use v5.10;

use Data::Dump 'dd';
use Getopt::Long;
use Mojo::Util qw(decamelize);
use Mojo::URL;
use OpenQA::Client;

Getopt::Long::Configure("no_ignore_case");

my %tables = map { $_ => 1 } qw(Machines TestSuites Products JobTemplates);

my %options;

sub usage($) {
    my $r = shift;
    eval "use Pod::Usage; pod2usage($r);";
    if ($@) {
        die "cannot display help, install perl(Pod::Usage)\n";
    }
    exit $_[0];
}

GetOptions(
    \%options,  "json",    "host=s",  "apibase=s",  "apikey:s",   "apisecret:s",
    "group=s@", "name=s@", "test=s@", "product=s@", "machine=s@", "full",
    "help|h",
) or usage(1);

usage(0) if $options{help};

if (@ARGV) {
    my %want = map { $_ => 1 } @ARGV;
    for my $t (keys %tables) {
        $tables{$t} = $want{$t} ? 1 : 0;
    }
}

if ($options{group}) {
    $options{group}       = {map { $_ => 1 } @{$options{group}}};
    $tables{JobTemplates} = 1;
}
if ($options{test}) {
    $options{test}      = {map { $_ => 1 } @{$options{test}}};
    $tables{TestSuites} = 1;
}
if ($options{machine}) {
    $options{machine} = {map { $_ => 1 } @{$options{machine}}};
    $tables{Machines} = 1;
}
if ($options{product}) {
    $options{product} = {map { $_ => 1 } @{$options{product}}};
    $tables{Products} = 1;
}

$options{'host'}    ||= 'localhost';
$options{'apibase'} ||= '/api/v1';

my $url;
if ($options{'host'} !~ '/') {
    $url = Mojo::URL->new();
    $url->host($options{'host'});
    $url->scheme('http');
}
else {
    $url = Mojo::URL->new($options{'host'});
}

my $client = OpenQA::Client->new(apikey => $options{'apikey'}, apisecret => $options{'apisecret'}, api => $url->host);

my %result;

for my $table (qw(Machines TestSuites Products JobTemplates)) {
    next unless $tables{$table};

    $url->path($options{'apibase'} . '/' . decamelize($table));
    my $res = $client->get($url)->res;
    if ($res->code && $res->code == 200) {
        if (!$res->json) {
            printf STDERR "ERROR requesting %s: response does not contain JSON\n", $table;
            printf STDERR "response: %s", $res->body if $res->body;
            exit(1);
        }
        my %tmp = (%result, %{$res->json});
        %result = %tmp;
    }
    else {
        printf STDERR "ERROR requesting %s: %s - %s\n", $table, $res->code // 'unknown error code',
          $res->message // 'no error message';
        if ($res->body) {
            dd($res->json || $res->body);
        }
        exit(1);
    }
}

# special trick to dump all TestSuites used by specific JobTemplates
if ($tables{JobTemplates} && $options{full}) {
    for my $r (@{$result{JobTemplates}}) {
        if ($options{group}) {
            next unless $options{group}->{$r->{group_name}};
        }
        $options{test}->{$r->{test_suite}->{name}} = 1;
        $options{machine}->{$r->{machine}->{name}} = 1;
        my $k = join('-', map { $r->{product}->{$_} } qw(distri version flavor arch));
        $options{product}->{$k} = 1;
    }
}

for my $table (keys %result) {
    my @r;
    while (my $r = shift @{$result{$table}}) {
        if ($table eq 'JobTemplates') {
            next if $options{group} && $r->{group_name} && !$options{group}->{$r->{group_name}};
            if ($options{product}) {
                my $k = join('-', map { $r->{product}->{$_} } qw(distri version flavor arch));
                next unless $options{product}->{$k};
            }
        }
        if ($table eq 'TestSuites' && $options{test} && $r->{name} && !$options{test}->{$r->{name}}) {
            next;
        }
        if ($table eq 'Machines' && $options{machine} && $r->{name} && !$options{machine}->{$r->{name}}) {
            next;
        }
        if ($table eq 'Products' && $options{product}) {
            my $k = join('-', map { $r->{$_} } qw(distri version flavor arch));
            next unless $options{product}->{$k};
        }

        delete $r->{id};
        if ($r->{settings}) {
            for my $s (0 .. $#{$r->{settings}}) {
                delete $r->{settings}->[$s]->{id};
            }
        }
        if ($r->{product}) {
            delete $r->{product}->{id};
        }
        if ($r->{machine}) {
            delete $r->{machine}->{id};
        }
        if ($r->{test_suite}) {
            delete $r->{test_suite}->{id};
        }
        push @r, $r;
    }
    $result{$table} = [@r];
}

if ($options{json}) {
    use Mojo::JSON;    # booleans
    use Cpanel::JSON::XS;
    print Cpanel::JSON::XS->new->ascii->pretty->encode(\%result);
}
else {
    dd \%result;
}
