#!/usr/bin/perl -w

use strict;
use warnings;

use File::Basename;
use YAML::XS qw/LoadFile/;
use XML::Writer;
use DateTime::HiRes;

use lib join("/", dirname($0), "lib");

use ExtRepoData;

use v5.36;

my @testcases;
my $tests = 0;
my $failures = 0;

my $full_start = DateTime::HiRes->now;

my $repos = {};

sub repotest($condition, $message) {
        $tests++;
	if($condition) {
		return { result => 1 };
	} else {
		say STDERR $message;
		$failures++;
		return { result => 0, message => $message };
	}
}

while(defined($ARGV[0])) {
	my $filedata = LoadFile(shift);
	foreach my $key(keys %$filedata) {
		next if $key =~ /^\./;
		die "duplicate repository name $key" if exists($repos->{$key});
		$repos->{$key} = $filedata->{$key};
	}
}

my %keys = (
	description => { ref => "", req => 1 },
	source => { ref => "HASH", req => 1 },
	suites => { ref => "ARRAY", req => 1 },
	components => { ref => "ARRAY", req => 0 },
	policies => { ref => "HASH", req => 0 },
	policy => { ref => "", req => 0 },
	contact => { ref => "", req => 0 },
	bugs => { ref => "", req => 0 },
	"gpg-key" => { ref => "", req => 1 },
	"onion-URIs" => { ref => "", req => 0 },
	Disabled => { ref => "", req => 0 },
	"post-enable-commands" => { ref => "ARRAY", req => 0 },
);

# These options mess with secure apt, and MUST NOT be used.
# Signed-By is added because it is managed by extrepo, and will be
# overridden if used.
my %forbidden = (
	"allow-insecure" => 1,
	"allow-weak" => 1,
	"allow-downgrade-to-insecure" => 1,
	"check-valid-until" => 1,
	"check-date" => 1,
	"trusted" => 1,
	"signed-by" => 1,
);

foreach my $repo(sort(keys %$repos)) {
	my $result = {
		starttime => DateTime::HiRes->now,
		name => "Check of repository $repo",
                id => "checkrepos.repository.$repo",
                tests => {},
	};
	my @component_keys = ( "components" );
	print STDERR "Checking repository $repo...\n";
	$result->{tests}{"repository name passes syntax check"} = repotest($repo =~ /^[a-z0-9_.-]+$/, "Invalid repository name $repo!");
	tie my(%hash), 'ExtRepoData', $repos->{$repo};
	$repos->{$repo} = \%hash;
	my $have_components = 0;
	foreach my $topkey(keys %{$repos->{$repo}}) {
		if($topkey =~ /suite-[^-]+-components/) {
			$have_components = 1;
			push @component_keys, $topkey;
			next;
		}
		$result->{tests}{"valid toplevel key $topkey"} = repotest(exists($keys{$topkey}), "invalid key $topkey");
		$result->{tests}{"type correct for toplevel key $topkey"} = repotest(ref($repos->{$repo}{$topkey}) eq $keys{$topkey}{ref}, "invalid type of key $topkey");
	}
	if($have_components || exists($repos->{$repo}{components})) {
		$have_components = 1;
		$keys{policies}{req} = 1;
		$keys{policy}{req} = 0;
	} else {
		$keys{policies}{req} = 0;
		$keys{policy}{req} = 1;
	}
	foreach my $reqd(keys %keys) {
		next unless $keys{$reqd}{req};
		$result->{tests}{"required toplevel key $reqd exists"} = repotest(exists($repos->{$repo}{$reqd}), "missing required key $reqd");
	}
	if($have_components) {
		foreach my $comp_key(@component_keys) {
			foreach my $component(@{$repos->{$repo}{$comp_key}}) {
				$result->{tests}{"policy for component $component is present"} = repotest(exists($repos->{$repo}{policies}{$component}), "no policy found for component $component");
			}
		}
	}
	my $types_found = 0;
	foreach my $src(keys %{$repos->{$repo}{source}}) {
		my $srcl = lc($src);
		if($srcl =~ /suite-[^-]*-(.*)/) {
			$srcl = $1;
		}
		$result->{tests}{"Deb822 field $src not forbidden"} = repotest(!exists($forbidden{$srcl}), "forbidden source option $src found");
		$result->{tests}{"Deb822 field $src is a scalar"} = repotest(ref($repos->{$repo}{source}{$src}) eq "", "source option $src is not a scalar");
		if ($srcl eq "types") {
			$types_found = 1;
			my $types_val = $repos->{$repo}{source}{$src};
			$result->{tests}{"types Deb822 field format is acceptable"} = repotest(($types_val eq "deb" || $types_val eq "deb deb-src"), "wrong value for source Types option: $types_val");
		}
	}
	$result->{tests}{"types Deb822 field present"} = repotest($types_found, "source Types option is missing");
	$result->{"time"} = DateTime::HiRes->now->hires_epoch - $result->{"starttime"}->hires_epoch;
        $result->{"timestamp"} = $result->{"starttime"}->iso8601;
	delete $result->{starttime};
	push @testcases, $result;
}

open my $junitfile, ">", "check-results-junit.xml";

my $junit = XML::Writer->new(OUTPUT => $junitfile);

$junit->startTag("testsuites", time => (DateTime::HiRes->now->hires_epoch - $full_start->hires_epoch), tests => $tests, failures => $failures);
foreach my $testcase(@testcases) {
	$junit->startTag("testsuite", name => $testcase->{name}, tests => scalar(keys %{$testcase->{tests}}), time => $testcase->{time}, timestamp => $testcase->{timestamp}, id => $testcase->{id});
	foreach my $testkey(keys %{$testcase->{tests}}) {
		$junit->startTag("testcase", name => $testkey);
		if(!$testcase->{tests}{$testkey}{result}) {
			$junit->startTag("failure", message => $testcase->{tests}{$testkey}{message});
			$junit->endTag("failure");
		}
		$junit->endTag("testcase");
	}
	$junit->endTag("testsuite");
}
$junit->endTag("testsuites");
$junit->end();
close $junitfile;
