#!/usr/bin/env perl

# Equivalence tests for utilities/yamlpatch against the YAMLPatch.pm API it wraps.
# The CLI is meant to be a thin argument-parsing layer over the same code path - these
# tests prove that, rather than just asserting it separately.

use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/..";
use YAMLPatch;
use File::Temp qw(tempdir);
use File::Copy qw(copy);

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; }
}

my $CLI = "$FindBin::Bin/../utilities/yamlpatch";
ok(-x $CLI, "utilities/yamlpatch is present and executable") or die "cannot continue without the CLI\n";

my $CONFIG = <<'YAML';
---
datasets:
  ds1:
    dataset: ds1
    source: storage/testing/src
    target: storage/testing/dst
    maxDelta: 0.9
  ds2:
    dataset: ds2
    source: storage/testing/src
    target: storage/testing/dst
    maxDelta: 0.9
debug: '0'
transport:
  compression:
    method: 'off'
    level: 6
YAML

sub run_cli {
    my (@args) = @_;
    my $out = `@args 2>&1`;
    return ($out, $? >> 8);
}

# ---------------------------------------------------------------------------
print "=== get: CLI output matches API value ===\n";
# ---------------------------------------------------------------------------
{
    my $dir = tempdir(CLEANUP => 1);
    my $path = "$dir/a.yaml";
    open my $fh, '>', $path or die $!; print {$fh} $CONFIG; close $fh;

    my ($out, $rc) = run_cli($CLI, 'get', 'transport.compression.method', $path);
    chomp $out;
    my $y = YAMLPatch->load_file($path);
    ok($rc == 0, 'get exits 0 for an existing path');
    ok($out eq $y->get('transport.compression.method')->{value}, "CLI get output ('$out') matches API get()");
}

# ---------------------------------------------------------------------------
print "\n=== get: missing path exits non-zero on both sides ===\n";
# ---------------------------------------------------------------------------
{
    my $dir = tempdir(CLEANUP => 1);
    my $path = "$dir/b.yaml";
    open my $fh, '>', $path or die $!; print {$fh} $CONFIG; close $fh;
    my (undef, $rc) = run_cli($CLI, 'get', 'does.not.exist', $path);
    ok($rc != 0, 'get on a missing path exits non-zero');
    my $y = YAMLPatch->load_file($path);
    ok(!defined($y->get('does.not.exist')), 'API get() on the same missing path returns undef');
}

# ---------------------------------------------------------------------------
print "\n=== set: CLI-written file is byte-identical to API-written file ===\n";
# ---------------------------------------------------------------------------
{
    my $dir = tempdir(CLEANUP => 1);
    my $cliPath = "$dir/cli.yaml";
    my $apiPath = "$dir/api.yaml";
    open my $fh, '>', $cliPath or die $!; print {$fh} $CONFIG; close $fh;
    copy($cliPath, $apiPath) or die $!;

    my (undef, $rc) = run_cli($CLI, 'set', 'transport.compression.method', "xz", $cliPath, '--style', 'single');
    ok($rc == 0, 'CLI set exits 0');

    my $y = YAMLPatch->load_file($apiPath);
    $y->set('transport.compression.method', 'xz', style => 'single');
    $y->save($apiPath);

    my $cliText = do { local (@ARGV, $/) = $cliPath; <> };
    my $apiText = do { local (@ARGV, $/) = $apiPath; <> };
    ok($cliText eq $apiText, 'CLI set() and API set() produce byte-identical files');
}

# ---------------------------------------------------------------------------
print "\n=== delete: CLI matches API ===\n";
# ---------------------------------------------------------------------------
{
    my $dir = tempdir(CLEANUP => 1);
    my $cliPath = "$dir/cli.yaml";
    my $apiPath = "$dir/api.yaml";
    open my $fh, '>', $cliPath or die $!; print {$fh} $CONFIG; close $fh;
    copy($cliPath, $apiPath) or die $!;

    run_cli($CLI, 'delete', 'datasets.ds1.maxDelta', $cliPath);
    my $y = YAMLPatch->load_file($apiPath);
    $y->delete('datasets.ds1.maxDelta');
    $y->save($apiPath);

    my $cliText = do { local (@ARGV, $/) = $cliPath; <> };
    my $apiText = do { local (@ARGV, $/) = $apiPath; <> };
    ok($cliText eq $apiText, 'CLI delete() and API delete() produce byte-identical files');
}

# ---------------------------------------------------------------------------
print "\n=== comment-out / uncomment: CLI matches API ===\n";
# ---------------------------------------------------------------------------
{
    my $dir = tempdir(CLEANUP => 1);
    my $cliPath = "$dir/cli.yaml";
    open my $fh, '>', $cliPath or die $!; print {$fh} $CONFIG; close $fh;

    run_cli($CLI, 'comment-out', 'datasets.ds2', $cliPath);
    my $afterComment = do { local (@ARGV, $/) = $cliPath; <> };
    ok($afterComment =~ /^\s*#ds2:/m, 'CLI comment-out actually comments the block');

    run_cli($CLI, 'uncomment', 'datasets.ds2', $cliPath);
    my $afterUncomment = do { local (@ARGV, $/) = $cliPath; <> };
    ok($afterUncomment eq $CONFIG, 'CLI comment-out followed by uncomment restores the original exactly');
}

# ---------------------------------------------------------------------------
print "\n=== --dry-run writes nothing ===\n";
# ---------------------------------------------------------------------------
{
    my $dir = tempdir(CLEANUP => 1);
    my $path = "$dir/c.yaml";
    open my $fh, '>', $path or die $!; print {$fh} $CONFIG; close $fh;
    my $before = do { local (@ARGV, $/) = $path; <> };

    my ($out, $rc) = run_cli($CLI, 'set', 'debug', '9', $path, '--style', 'single', '--dry-run');
    ok($rc == 0, '--dry-run exits 0');
    ok($out =~ /debug/, '--dry-run prints a diff mentioning the changed key');

    my $after = do { local (@ARGV, $/) = $path; <> };
    ok($before eq $after, '--dry-run left the file completely untouched');
}

# ---------------------------------------------------------------------------
print "\n=== --backup writes a timestamped backup before saving ===\n";
# ---------------------------------------------------------------------------
{
    my $dir = tempdir(CLEANUP => 1);
    my $path = "$dir/d.yaml";
    open my $fh, '>', $path or die $!; print {$fh} $CONFIG; close $fh;

    run_cli($CLI, 'set', 'debug', '9', $path, '--style', 'single', '--backup');
    my @backups = glob("$dir/d.yaml.bak.*");
    ok(scalar(@backups) == 1, 'exactly one backup file created');
    my $backupText = do { local (@ARGV, $/) = $backups[0]; <> };
    ok($backupText eq $CONFIG, 'backup contains the pre-mutation content');
}

# ---------------------------------------------------------------------------
print "\n=== exit codes ===\n";
# ---------------------------------------------------------------------------
{
    my (undef, $rcHelp) = run_cli($CLI, '--help');
    ok($rcHelp == 0, '--help exits 0');

    my (undef, $rcUsage) = run_cli($CLI);
    ok($rcUsage == 2, 'no arguments exits 2 (usage error)');

    my $dir = tempdir(CLEANUP => 1);
    my $path = "$dir/e.yaml";
    open my $fh, '>', $path or die $!; print {$fh} $CONFIG; close $fh;
    my (undef, $rcRefuse) = run_cli($CLI, 'set', 'debug', 'off', $path, '--style', 'bare');
    ok($rcRefuse == 1, 'a refused operation (bare YAML-boolean word) exits 1, not 2 or 0');
}

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