package PVE::Diskmanage::Test;

use strict;
use warnings;

use lib qw(..);

use PVE::Diskmanage;
use PVE::Tools;

use Test::MockModule;
use Test::More;
use JSON;
use Data::Dumper;

my $testcasedir; # current case directory
my $testcount = 0; # testcount for TAP::Harness
my $diskmanage_module; # mockmodule for PVE::Diskmanage
my $print = 0;

sub mocked_run_command {
    my ($cmd, %param) = @_;

    my $outputlines = [];
    if (my $ref = ref($cmd)) {
	if ($cmd->[0] =~ m/udevadm/i) {
	    # simulate udevadm output
	    my $dev = $cmd->[3];
	    $dev =~ s|/sys/block/||;
	    @$outputlines = split(/\n/, read_test_file("${dev}_udevadm"));

	} elsif ($cmd->[0] =~ m/smartctl/i) {
	    # simulate smartctl output
	    my $dev;
	    my $type;
	    if (@$cmd > 3) {
		$dev = $cmd->[5];
		$type = 'smart';
	    } else {
		$dev = $cmd->[2];
		$type = 'health';
	    }
	    $dev =~ s|/dev/||;
	    @$outputlines = split(/\n/, read_test_file("${dev}_${type}"));
	} elsif ($cmd->[0] =~ m/sgdisk/i) {
	    # simulate sgdisk
	    die "implement me: @$cmd\n";
	} elsif ($cmd->[0] =~ m/zpool/i) {
	    # simulate zpool output
	    @$outputlines = split(/\n/, read_test_file('zpool'));

	} elsif ($cmd->[0] =~ m/pvs/i) {
	    # simulate lvs output
	    @$outputlines = split(/\n/, read_test_file('pvs'));
	} else {
	    print "unexpected run_command call: '@$cmd', aborting\n";
	    die;
	}
    } else {
	print "unexpected run_command call: '@$cmd', aborting\n";
	die;
    }

    my $outfunc;
    if ($param{outfunc}) {
	$outfunc = $param{outfunc};
	map { &$outfunc(($_)) } @$outputlines;

	return 0;
    }
}

sub mocked_get_sysdir_info {
    my ($param) = @_;

    my $originalsub = $diskmanage_module->original('get_sysdir_info');

    $param =~ s|/sys/block|disk_tests/$testcasedir|;

    return &$originalsub($param);
}

sub mocked_is_iscsi {
    return 0;
}

sub mocked_dir_glob_foreach {
    my ($dir, $regex, $sub) = @_;

    my $lines = [];

    # read lines in from file
    if ($dir =~ m{^/sys/block$} ) {
	@$lines = split(/\n/, read_test_file('disklist'));
    } elsif ($dir =~ m{^/sys/block/([^/]+)}) {
	@$lines = split(/\n/, read_test_file('partlist'));
    }

    foreach my $line (@$lines) {
	if ($line =~ m/$regex/) {
	    &$sub($line);
	}
    }
}

sub mocked_parse_proc_mounts {
    my $text = read_test_file('mounts');

    my $mounts = [];

    foreach my $line(split(/\n/, $text)) {
	push @$mounts, [split(/\s+/, $line)];
    }

    return $mounts;
}

sub read_test_file {
    my ($filename) = @_;

    if (!-f  "disk_tests/$testcasedir/$filename") {
	print "file '$testcasedir/$filename' not found\n";
	return '';
    }
    open (my $fh, '<', "disk_tests/$testcasedir/$filename")
	or die "Cannot open disk_tests/$testcasedir/$filename: $!";

    my $output = <$fh> // '';
    chomp $output if $output;
    while (my $line = <$fh>) {
	chomp $line;
	$output .= "\n$line";
    }

    return $output;
}


sub test_disk_list {
    my ($testdir) = @_;
    subtest "Test '$testdir'" => sub {
	my $testcount = 0;
	$testcasedir = $testdir;

	my $disks;
	my $expected_disk_list;
	eval {
	    $disks = PVE::Diskmanage::get_disks();
	};
	warn $@ if $@;
	$expected_disk_list = decode_json(read_test_file('disklist_expected.json'));

	print Dumper($disks) if $print;
	$testcount++;
	is_deeply($disks, $expected_disk_list, 'disk list should be the same');

	foreach my $disk (sort keys %$disks) {
	    my $smart;
	    my $expected_smart;
	    eval {
		$smart = PVE::Diskmanage::get_smart_data("/dev/$disk");
		print Dumper($smart) if $print;
		$expected_smart = decode_json(read_test_file("${disk}_smart_expected.json"));
	    };

	    if ($smart && $expected_smart) {
		$testcount++;
		is_deeply($smart, $expected_smart, "smart data for '$disk' should be the same");
	    } elsif ($smart && -f  "disk_tests/$testcasedir/${disk}_smart_expected.json") {
		$testcount++;
		ok(0,  "could not parse expected smart for '$disk'\n");
	    }
	}

	done_testing($testcount);
    };
}

# start reading tests:

if (@ARGV && $ARGV[0] eq 'print') {
    $print = 1;
}

print("Setting up Mocking\n");
$diskmanage_module =  new Test::MockModule('PVE::Diskmanage', no_auto => 1);
$diskmanage_module->mock('run_command' => \&mocked_run_command);
print("\tMocked run_command\n");
$diskmanage_module->mock('dir_glob_foreach' => \&mocked_dir_glob_foreach);
print("\tMocked dir_glob_foreach\n");
$diskmanage_module->mock('get_sysdir_info' => \&mocked_get_sysdir_info);
print("\tMocked get_sysdir_info\n");
$diskmanage_module->mock('is_iscsi' => \&mocked_is_iscsi);
print("\tMocked is_iscsi\n");
$diskmanage_module->mock('assert_blockdev' => sub { return 1; });
print("\tMocked assert_blockdev\n");
$diskmanage_module->mock('dir_is_empty' => sub {
	# all partitions have a holder dir
	my $val = shift;
	if ($val =~ m|^/sys/block/.+/.+/|) {
	    return 0;
	}
	return 1;
    });
print("\tMocked dir_is_empty\n");
my $tools_module= new Test::MockModule('PVE::ProcFSTools', no_auto => 1);
$tools_module->mock('parse_proc_mounts' => \&mocked_parse_proc_mounts);
print("\tMocked parse_proc_mounts\n");
print("Done Setting up Mocking\n\n");

print("Beginning Tests:\n\n");
opendir (my $dh, 'disk_tests')
    or die "Cannot open disk_tests: $!";

while (readdir $dh) {
    my $dir = $_;
    next if $dir eq '.' or $dir eq '..';
    $testcount++;
    test_disk_list($dir);
}

done_testing($testcount);
