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

use Test::Most;
use Test::Warnings qw(:report_warnings warning);
use Mojo::Base -signatures;

use FindBin;
use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib";

use OpenQA::Test::TimeLimit '7';
use OpenQA::CLI;
use OpenQA::CLI::monitor;
use OpenQA::CLI::schedule;
use OpenQA::CLI::api;
use OpenQA::Jobs::Constants;
use OpenQA::Test::Case;
use OpenQA::Test::Utils qw(perform_minion_jobs);
use OpenQA::Schema::Result::ScheduledProducts;
use Mojo::Server::Daemon;
use Mojo::File qw(tempdir tempfile);
use Test::Output qw(combined_like);
use Test::MockModule;

$ENV{OPENQA_CLI_RETRIES} = 0;

my $schema = OpenQA::Test::Case->new->init_data(fixtures_glob => '03-users.pl');
my $schedule = OpenQA::CLI::schedule->new;

# change API to simulate job state/result changes
my $job_controller_mock = Test::MockModule->new('OpenQA::WebAPI::Controller::API::V1::Job');
my @job_mock_results = (PASSED, SOFTFAILED, PASSED, USER_CANCELLED, PASSED, SOFTFAILED, PASSED, SOFTFAILED);
$job_controller_mock->redefine(
    get_status => sub ($self) {
        # reply as usual
        $job_controller_mock->original('get_status')->($self);
        # fake that the job is done which will affect the next status query
        my $job = $self->schema->resultset('Jobs')->find(int($self->stash('jobid')));
        $job->done(result => (shift(@job_mock_results) // PASSED), reason => 'mocked') if $job && $job->result eq NONE;
    });

# start web server
my $daemon = Mojo::Server::Daemon->new(listen => ['http://127.0.0.1']);
my $app = $daemon->build_app('OpenQA::WebAPI');
my $port = $daemon->start->ports->[0];
my $host = "http://127.0.0.1:$port";
$app->log->level($ENV{HARNESS_IS_VERBOSE} ? 'debug' : 'error');
$app->config->{'scm git'}->{git_auto_update} = 'no';

my $fake_scheduled_product_status;
my $iso_controller_mock = Test::MockModule->new('OpenQA::WebAPI::Controller::API::V1::Iso');
$iso_controller_mock->redefine(
    create => sub ($self) {
        # reply as usual
        $iso_controller_mock->original('create')->($self);
        # schedule the product immediately or fake the status
        return perform_minion_jobs $app->minion unless $fake_scheduled_product_status;
        my $sp = $self->schema->resultset('ScheduledProducts')->search({}, {order_by => {-desc => 'id'}, rows => 1});
        $sp->update({status => $fake_scheduled_product_status});
    });

combined_like { OpenQA::CLI->new->run('help', 'schedule') } qr/Usage: openqa-cli schedule/, 'help';
subtest 'unknown options' => sub {
    like warning {
        throws_ok { $schedule->run('--unknown') } qr/Usage: openqa-cli schedule/, 'unknown option';
    }, qr/Unknown option: unknown/, 'right output';
};

# define different sets of CLI args to be used in further tests
my @basic_options = ('--apikey', 'ARTHURKEY01', '--apisecret', 'EXCALIBUR', '--host', $host, '-i', 0);
my @options = (@basic_options, '-m');
my @scenarios = ('--param-file', "SCENARIO_DEFINITIONS_YAML=$FindBin::Bin/data/09-schedule_from_file.yaml");
my @settings1 = (qw(DISTRI=example VERSION=0 FLAVOR=DVD ARCH=x86_64 TEST=simple_boot));
my @settings2 = (qw(DISTRI=opensuse VERSION=13.1 FLAVOR=DVD ARCH=i586 BUILD=0091 TEST=autoyast_btrfs));
my @settings3 = (@settings2, qw(async=1));
my @settings4 = (qw(distri=opensuse version=13.1 flavor=DVD arch=i586 build=0091 test=autoyast_btrfs));

subtest 'running into error reply' => sub {
    my $res;
    combined_like { $res = $schedule->run(@options) } qr/Error: missing parameters: DISTRI VERSION FLAVOR ARCH/,
      '"missing parameters" error';
    is $schedule->host, $host, 'host set';
    is $res, 1, 'non-zero return-code if parameters missing';

    combined_like { $res = $schedule->run(@options, @settings1) } qr/no products found for example-DVD-x86_64/,
      '"no products found" error';
    is $res, 1, 'non-zero return-code if no products could be found';
};

subtest 'scheduling and monitoring zero-sized set of jobs' => sub {
    my $res;
    combined_like { $res = $schedule->run(@options, @scenarios, @settings1) } qr/count.*0/, 'response logged';
    is $res, 0, 'zero return-code';
};

subtest 'scheduling and monitoring set of two jobs' => sub {
    my $res;
    combined_like { $res = $schedule->run(@options, @scenarios, @settings2) }
    qr|2 jobs have been created.*(http://127.0.0.1.*/tests/\d+.*){2}passed.*softfailed|s,
      'response logged if all jobs are ok';
    is $res, 0, 'zero return-code if all jobs are ok';

    combined_like { $res = $schedule->run(@options, @scenarios, @settings2) } qr/count.*2.*passed.*user_cancelled/s,
      'response logged if one job was cancelled';
    is $res, 2, 'non-zero return-code if at least one job is not ok';

    combined_like { $res = $schedule->run(@options, @scenarios, @settings3) }
    qr|"successful_job_ids":\[\d+,\d+\].*passed.*softfailed|s, 'response logged if async=1 flag was used';
    is $res, 0, 'zero return-code if all jobs are ok with async=1 flag';

    combined_like { $res = $schedule->run(@options, @scenarios, @settings4) }
    qr|2 jobs have been created.*(http://127.0.0.1.*/tests/\d+.*){2}passed.*softfailed|s,
      'response logged if jobs created correctly from lower-case arguments';
    is $res, 0, 'zero return-code if all jobs are ok with lower-case arguments';

    $fake_scheduled_product_status = OpenQA::Schema::Result::ScheduledProducts::CANCELLED;
    combined_like { $res = $schedule->run(@options, @scenarios, @settings3) }
    qr|Scheduled product \d+ ended up cancelled|s,
      'response logged if scheduled product was cancelled when async=1 flag was used';
    is $res, 1, 'non-zero return-code if scheduled product was cancelled with async=1 flag';
};

subtest 'monitor jobs as a separate command' => sub {
    my $res;
    my $monitor = OpenQA::CLI::monitor->new;
    my $jobs = $schema->resultset('Jobs');
    $jobs->create({id => $_, TEST => "test-$_"}) for (100 .. 103);
    @job_mock_results = (PASSED, SOFTFAILED);
    undef $fake_scheduled_product_status;
    combined_like { $res = $monitor->run(@basic_options, 100, 101) } qr/100.*101/s, 'status logged (passing case)';
    is $res, 0, 'zero return-code if all jobs ok';
    @job_mock_results = (PASSED, FAILED);
    combined_like { $res = $monitor->run(@basic_options, 102, 103) } qr/102.*103/s, 'status logged (failing case)';
    is $res, 2, 'none-zero return-code if one job failed';
    $jobs->create({id => 105, TEST => 'test-105', result => PASSED, state => DONE});
    $jobs->create({id => 104, TEST => 'test-104', clone_id => 105, result => INCOMPLETE, state => DONE});
    $job_controller_mock->unmock('get_status');
    combined_like { $res = $monitor->run(@basic_options, '-f', 104) } qr/105/s, 'followed job via clone';
    is $res, 0, 'zero return-code if clone of followed jobs ok';
};

subtest 'api command preserves lowercase keys for routes expecting them' => sub {
    my $res;
    my $api = OpenQA::CLI::api->new;
    my @api_options = ('--apikey', 'ARTHURKEY01', '--apisecret', 'EXCALIBUR', '--host', $host);
    combined_like {
        $res = $api->run(@api_options, 'isos/job_stats', 'distri=example', 'version=0', 'flavor=DVD', 'arch=x86_64',
            'build=123')
    }
    qr/\{\}/s, 'response logged successfully because lower-case arguments were preserved';
    is $res, 0, 'zero return-code for successful api call with lower-case arguments';
};

done_testing();
