#!/usr/bin/env perl

# Unit tests for Harness::Report, exercised end-to-end against Harness::Runner's real
# result shape (not hand-typed fixtures for the result structure) so a schema drift between
# the two modules is caught here rather than only at the first real run.

use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/..";
use lib "$FindBin::Bin/../sneakernet/testing";
use Harness::Catalogue;
use Harness::Runner;
use Harness::Safe;
use Harness::Report;
use File::Temp qw(tempdir);
use File::Path qw(make_path);
use JSON::PP qw(encode_json);

my $passed = 0;
my $failed = 0;

sub ok {
    my ($cond, $desc) = @_;
    if ($cond) { print "  PASS: $desc\n"; $passed++; return 1; }
    else       { print "  FAIL: $desc\n"; $failed++; return 0; }
}

sub write_json { my ($path, $data) = @_; open my $fh, '>', $path or die $!; print {$fh} encode_json($data); close $fh; }

my $CONFIG_TEXT = "---\ndebug: '0'\n";

my $sandbox = tempdir(CLEANUP => 1);
make_path("$sandbox/transport");
my $configPath = "$sandbox/sneakernet.conf.yaml";
open my $fh, '>', $configPath or die $!; print {$fh} $CONFIG_TEXT; close $fh;

my $catDir = tempdir(CLEANUP => 1);
write_json("$catDir/reset_profiles.json", { standard => [], none => [] });
write_json("$catDir/part01.json", {
    part => 1, title => 'Basic sanity', reset => 'standard',
    steps => [ { id => '1.1', title => 'a', assertions => [ { type => 'true', var => 'X', message => 'ok' } ], actions => [ { do => 'write_file', path => "$sandbox/f", content => 'x', store_as => 'X' } ] } ],
});
write_json("$catDir/part02.json", {
    part => 2, title => 'A part that fails', reset => 'standard',
    steps => [ { id => '2.1', title => 'b', assertions => [ { type => 'true', var => 'NEVER', message => 'deliberately fails' } ] } ],
});
write_json("$catDir/part19.json", {
    part => 19, title => 'Cleanup', reset => 'none',
    steps => [ { id => '19.0', title => 'teardown', classification => 'manual', manual_note => 'run the manual teardown block' } ],
});

my $cat = Harness::Catalogue->load_dir($catDir);
my $runner = Harness::Runner->new(catalogue => $cat, config_path => $configPath);
local $Harness::Safe::SANDBOX_PATH = $sandbox;
Harness::Safe::set_mode('run');
$runner->run_steps($cat->all_step_ids);

my %partTitles = map { $_->{part} => $_->{title} } $cat->parts;
my $report = Harness::Report::render(
    run_id => 'r_test', start_time => 1000, end_time => 1123,
    host => 'dd-nas1', svn_rev => '165', mode => 'run',
    sneakernet_version => '1.10.11', zfsutils_version => '1.7.1', harness_version => '0.1.0',
    baseline_maxdelta => 'commented', include_optional => 0,
    results => [ $runner->results ], part_titles => \%partTitles,
    followups => [ 'Step 8.5 (known broken, not fixed this pass)', 'maxDelta spurious aborts (not fixed this pass)' ],
);

ok(length($report) > 0, 'render() produces non-empty output');
ok($report =~ /Run id:\s+r_test/, 'header includes the run id');
ok($report =~ /Duration:\s+0h 2m 3s/, 'duration is computed and formatted correctly');
ok($report =~ /\*\*\* DEVIATION/, 'a non-default --baseline-maxdelta is flagged as a deviation in the header');
ok($report =~ /\[PASS\s*\]\s+1\.1/, 'a passing step appears with PASS status');
ok($report =~ /\[FAIL\s*\]\s+2\.1/, 'a failing step appears with FAIL status');
ok($report =~ /FAILED: deliberately fails/, 'a failed assertion message is included for diagnosis');
ok($report =~ /\[N\/A\s*\]\s+19\.0/, 'a manual step appears with N/A status');
ok($report =~ /run the manual teardown block/, "a manual step's note is included");
ok($report =~ /Basic sanity\s+PASS/, 'Part 1 summarizes as PASS');
ok($report =~ /A part that fails\s+FAIL/, 'Part 2 summarizes as FAIL');
ok($report =~ /Cleanup\s+N\/A/, 'Part 19 (manual-only) summarizes as N/A');
ok($report =~ /PASS\s+1\n/, 'totals count exactly one PASS step');
ok($report =~ /FAIL\s+1\n/, 'totals count exactly one FAIL step');
ok($report =~ /FOLLOW-UP/, 'the follow-up section is present when followups are given');
ok($report =~ /Step 8\.5/, 'the follow-up section lists the deferred Step 8.5 item');

# ===========================================================================
print "\n=== a Part mixing EXPLAINED and SKIP (an optional step, --explain) summarizes as EXPLAINED, not PASS ===\n";
# ===========================================================================
{
    my $catDir2 = tempdir(CLEANUP => 1);
    write_json("$catDir2/reset_profiles.json", { standard => [], none => [] });
    write_json("$catDir2/part03.json", {
        part => 3, title => 'Mixed explain and skip', reset => 'standard',
        steps => [
            { id => '3.1', title => 'a normal step', actions => [ { do => 'write_file', path => "$sandbox/g", content => 'x' } ] },
            { id => '3.2', title => 'an optional step', classification => 'optional', actions => [] },
        ],
    });
    my $cat2 = Harness::Catalogue->load_dir($catDir2);
    my $runner2 = Harness::Runner->new(catalogue => $cat2, config_path => $configPath);
    local $Harness::Safe::SANDBOX_PATH = $sandbox;
    Harness::Safe::set_mode('explain');
    $runner2->run_steps($cat2->all_step_ids);
    my %partTitles2 = map { $_->{part} => $_->{title} } $cat2->parts;
    my $report2 = Harness::Report::render(
        run_id => 'r_test2', start_time => 1000, end_time => 1010,
        host => 'dd-nas1', svn_rev => '165', mode => 'explain',
        sneakernet_version => '1.10.11', zfsutils_version => '1.7.1', harness_version => '0.1.0',
        baseline_maxdelta => 'commented', include_optional => 0,
        results => [ $runner2->results ], part_titles => \%partTitles2,
        followups => [],
    );
    Harness::Safe::set_mode('run');
    ok($report2 =~ /\[EXPLAINED\]\s+3\.1/, 'the normal step shows EXPLAINED');
    ok($report2 =~ /\[SKIP\s*\]\s+3\.2/, 'the optional step shows SKIP (no --include-optional)');
    ok($report2 =~ /Mixed explain and skip\s+EXPLAINED/, 'the part summarizes as EXPLAINED, not a misleading bare PASS, since nothing in it actually ran')
        or print "    report was:\n$report2\n";
}

print "\n=== Summary ===\n";
print "Passed: $passed\n";
print "Failed: $failed\n";
exit($failed > 0 ? 1 : 0);
