#!/usr/bin/env perl

# Copyright 2015-2021 SUSE LLC
# SPDX-License-Identifier: GPL-2.0-or-later

=head1 NAME

openqa-load-templates - load openqa job templates

=head1 SYNOPSIS

openqa-load-templates [OPTIONS] FILE

=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<--clean>

delete all job templates before loading new ones. be careful!

=item B<--update>

update existing entries (by default, existing entries are not changed)

=item B<--help, -h>

print help

=back

=head1 DESCRIPTION

lorem ipsum ...

=cut

use Mojo::Base -strict, -signatures;

use FindBin;
use lib "$FindBin::RealBin/../lib";
use File::Basename qw(dirname);
use Feature::Compat::Try;
use Data::Dump 'dd';
use Mojo::Util qw(decamelize);
use Mojo::URL;
use OpenQA::Client;
#use OpenQA::Script::Client;
use Mojo::JSON;    # booleans
use Cpanel::JSON::XS ();

use Getopt::Long;

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

my %options;

sub usage ($r) { require Pod::Usage; Pod::Usage::pod2usage($r) }

GetOptions(\%options, "apibase=s", "apikey=s", "apisecret=s", "clean", "host=s", "update", "help|h",) or usage(1);

usage(0) if $options{help};
usage(1) if $#ARGV;

# Slurp file specified as an argument or STDIN in case of "-"
my $datafile = join("", <>);
die "Data file not found\n" unless length $datafile;

my $info;

try {
    $info = Cpanel::JSON::XS->new->relaxed->decode($datafile);
    dd $info;
}
catch ($e) {
    $info = eval $datafile;
    if (my $error = $@) { die "Error in data file: $error\n" }
}

$options{'host'} ||= 'localhost';
$options{apibase} ||= '/api/v1';
my $url = OpenQA::Client::url_from_host($options{host});
my $client = OpenQA::Client->new(apikey => $options{'apikey'}, apisecret => $options{'apisecret'}, api => $url->host);
my @tables = (qw(Machines TestSuites Products JobTemplates JobGroups));
# clean JobGroups first so we don't hit
# "must be updated through the YAML template" in the other tables
my @cleantables = (qw(JobGroups Machines TestSuites Products JobTemplates));

sub print_error ($res) {
    if (my $err = $res->error) {
        if (my $json = $res->json) {
            if (my $json_err = $json->{error}) {
                die "$err->{message}: " . join("\n", @{$json_err})
                  if ref $json_err eq 'ARRAY';
                die "$err->{message}: $json_err";
            }
        }
        die "ERROR: $err->{code} - $err->{message}" if $err->{code};
    }
    die "unknown error code - host $url->{host} unreachable?";
}

sub post_yaml_templates ($group_name, $template) {
    # Post the job template YAML
    my $job_templates_url = $url->clone->path($options{apibase} . '/job_templates_scheduling');
    return $client->post(
        $job_templates_url,
        form => {
            name => $group_name,
            template => $template,
            schema => 'JobTemplates-01.yaml'
        })->res;
}

sub post_entry ($table, $entry) {
    my %param;

    if ($table eq 'JobGroups') {
        # Try to create the group first
        my $job_groups_url = $url->clone->path($options{apibase} . '/job_groups');
        my $create_res = $client->post($job_groups_url, form => {name => $entry->{group_name}})->res;
        # this is what we get from the API if the group exists
        my $exists = ($create_res->code == 500 && $create_res->json->{already_exists});
        # return 0 (indicating no change) unless --clean or --update passed
        return 0 if ($exists && !$options{update} && !$options{clean});
        print_error($create_res) unless ($create_res->is_success || $exists);
        # Post the job template YAML

        my $yaml_res = post_yaml_templates($entry->{group_name}, $entry->{template});
        print_error($yaml_res) unless $yaml_res->is_success;
        return 1;
    }

    if ($table eq 'JobTemplates') {
        unless (defined($entry->{prio})) {
            # we have to migrate the prio from the TestSuite to the JobTemplate
            for my $ts (@{$info->{TestSuites}}) {
                if ($ts->{name} eq $entry->{test_suite}{name}) {
                    $entry->{prio} = $ts->{prio};
                }
            }
        }
        unless (defined($entry->{group_name})) {
            # we have to create a group_name from the Product
            my $gn = $entry->{product}{distri};
            if ($entry->{product}{version} ne '*') {
                $gn .= "-" . $entry->{product}{version};
            }
            $entry->{group_name} = $gn;
        }
    }

    for my $key (keys %{$entry}) {
        if ($key eq 'machine' && defined $entry->{machine}{name}) {
            $param{machine_name} = $entry->{machine}{name};
        }
        elsif ($key eq 'test_suite' && $entry->{test_suite}{name}) {
            $param{test_suite_name} = $entry->{test_suite}{name};
        }
        elsif ($key eq 'product' && ref($entry->{product}) eq "HASH") {
            $param{arch} = $entry->{product}{arch};
            $param{distri} = $entry->{product}{distri};
            $param{flavor} = $entry->{product}{flavor};
            $param{version} = $entry->{product}{version};
        }
        elsif ($key eq 'settings' && ref($entry->{settings}) eq "ARRAY") {
            $param{settings} = {};
            for my $var (@{$entry->{settings}}) {
                $param{settings}{$var->{key}} = $var->{value};
            }
        }
        else {
            $param{$key} = $entry->{$key};
        }
    }

    my $table_url = $url->clone->path($options{apibase} . '/' . decamelize($table));
    if (!$options{clean}) {    # with --clean the entry should not exist at this point, no need to check
        my $res = $client->get($table_url->clone, form => \%param)->res;
        if ($res->is_success && @{$res->json->{$table}} > 0 && $res->json->{$table}[0]{id}) {
            if ($options{update} && $table ne 'JobTemplates')
            {    # there is nothing to update in JobTemplates, the entry just exists or not
                my $id = $res->json->{$table}[0]{id};
                my $table_url_id = $url->clone->path($options{apibase} . '/' . decamelize($table) . "/$id");
                my $res = $client->put($table_url_id, json => \%param)->res;
                print_error $res unless $res->is_success;
                return 1;
            }
            else {
                return 0;    # already exists
            }
        }
    }

    my $type = $table eq 'JobTemplates' ? 'form' : 'json';
    my $res = $client->post($table_url, $type => \%param)->res;
    print_error $res unless $res->is_success;
    return 1;
}

if ($options{'clean'}) {
    for my $table (@cleantables) {
        my $table_url = $url->clone->path($options{apibase} . '/' . decamelize($table));
        my $res = $client->get($table_url)->res;
        if ($res->is_success) {
            my $result = $res->json;
            # we can't clean job groups as they're not deletable
            # unless empty, but we *can* replace existing YAML
            # template strings with 'empty' ones
            if ($table eq "JobGroups") {
                # unlike all other tables, is not a hash
                for my $jg (@$result) {
                    next unless ($jg->{template});
                    $res = post_yaml_templates($jg->{name}, "scenarios: {}\nproducts: {}\n");
                    last unless $res->is_success;
                }
            }
            else {
                for my $i (0 .. $#{$result->{$table}}) {
                    my $id = $result->{$table}->[$i]->{id};
                    my $table_url_id = $url->clone->path($options{apibase} . '/' . decamelize($table) . "/$id");
                    $res = $client->delete($table_url_id)->res;
                    last unless $res->is_success;
                }
            }
        }
        print_error $res unless $res->is_success;
    }
}

my %added;
for my $table (@tables) {
    next unless $info->{$table};
    $added{$table}->{of} = @{$info->{$table}};
    $added{$table}->{added} = 0;
    for my $entry (@{$info->{$table}}) {
        if (post_entry($table, $entry)) {
            ++$added{$table}->{added};
        }
    }
}

dd \%added;
