#!/usr/bin/perl

use strict;
use warnings;

use lib qw(..);
use File::Slurp;
use NetAddr::IP qw(:lower);

use Test::More;
use Test::MockModule;

use PVE::Network::SDN;
use PVE::Network::SDN::Zones;
use PVE::Network::SDN::Controllers;
use PVE::INotify;
use JSON;

use Data::Dumper qw(Dumper);
$Data::Dumper::Sortkeys = 1;

sub read_sdn_config {
    my ($file) = @_;
    # Read structure back in again
    open my $in, '<', $file or die $!;
    my $sdn_config;
    {
	local $/;    # slurp mode
	$sdn_config = eval <$in>;
    }
    close $in;
    return $sdn_config;
}


my @plugins = read_dir( './vnets/', prefix => 1 ) ;

foreach my $path (@plugins) {

    my (undef, $testid) = split(/\//, $path);

    print "test: $testid\n";
    my $sdn_config = read_sdn_config ("$path/sdn_config");

    my $pve_sdn_zones;
    $pve_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones');
    $pve_sdn_zones->mock(
	config => sub {
	    return $sdn_config->{zones};
	},
    );

    my $pve_sdn_vnets;
    $pve_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets');
    $pve_sdn_vnets->mock(
	config => sub {
	    return $sdn_config->{vnets};
	},
    );

    my $pve_sdn_subnets;
    $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
    $pve_sdn_subnets->mock(
	config => sub {
	    return $sdn_config->{subnets};
	},
	verify_dns_zone => sub {
	    return;
	},
	add_dns_record => sub {
	    return;
	}
    );

    my $js = JSON->new;
    $js->canonical(1);
  
    #test params;
    #test params;
    my $subnets = $sdn_config->{subnets}->{ids};

    my $subnetid = (sort keys %{$subnets})[0];
    my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
    my $subnet_cidr = $subnet->{cidr};
    my $iplist = NetAddr::IP->new($subnet_cidr);
    my $mask = $iplist->masklen();
    my $ipversion = undef;

    if (Net::IP::ip_is_ipv4($iplist->canon())){
	$iplist++; #skip network address for ipv4
	$ipversion = 4;
    } else { 
	$ipversion = 6;
    }

    my $cidr1 = $iplist->canon()."/$mask";
    $iplist++;
    my $cidr2 = $iplist->canon()."/$mask";
    my $cidr_outofrange = '8.8.8.8/8';

    my $subnetid2 = (sort keys %{$subnets})[1];
    my $subnet2 = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid2, 1);
    my $subnet2_cidr = $subnet2->{cidr};
    my $iplist2 = NetAddr::IP->new($subnet2_cidr);
    $iplist2++;
    my $cidr3 = $iplist2->canon()."/$mask";
    $iplist2++;
    my $cidr4 = $iplist2->canon()."/$mask";

    my $hostname = "myhostname";
    my $mac = "da:65:8f:18:9b:6f";
    my $description = "mydescription";
    my $ipamdb = read_sdn_config ("$path/ipam.db");

    my $zone = $sdn_config->{zones}->{ids}->{"myzone"};
    my $ipam = $zone->{ipam};

    my $plugin;
    my $sdn_ipam_plugin;
    if($ipam) {
	$plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam);
	$sdn_ipam_plugin = Test::MockModule->new($plugin);
	$sdn_ipam_plugin->mock(
	    read_db => sub {
		return $ipamdb;
	    },
	    write_db => sub {
		my ($cfg) = @_;
		$ipamdb = $cfg;
	    }
	);
    }

    my $pve_sdn_ipams;
    $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
    $pve_sdn_ipams->mock(
	config => sub {
	    my $ipam_config = read_sdn_config ("$path/ipam_config");
	    return $ipam_config;
	},
    );

    my $vnetid = "myvnet";

    ## add_ip
    my $test = "add_cidr $cidr1";
    my $name = "$testid $test";
    my $result = undef;
    my $expected = '';

    eval {
	PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

    ## add_ip
    $test = "add_already_exist_cidr $cidr1";
    $name = "$testid $test";
    $result = undef;
    $expected = '';

    eval {
	PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
    };

    if ($@) {
        is (undef, undef, $name);
    } elsif($ipam) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

    ## add_ip
    $test = "add_cidr $cidr2";
    $name = "$testid $test";
    $result = undef;
    $expected = '';

    eval {
	PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr2, $hostname, $mac, $description);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

    ## add_ip
    $test = "add_ip_out_of_range_subnets $cidr_outofrange";
    $name = "$testid $test";
    $result = undef;
    $expected = '';

    eval {
	PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr_outofrange, $hostname, $mac, $description);
    };

    if ($@) {
        is (undef, undef, $name);
    } else {
        fail("$name : $@");
    }

    ## add_ip
    $test = "add_cidr $cidr4";
    $name = "$testid $test";
    $result = undef;
    $expected = '';

    eval {
	PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr4, $hostname, $mac, $description);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }


    $test = "find_next_free_cidr_in_second_subnet ($cidr3)";
    $name = "$testid $test";
    $result = undef;
    $expected = $ipam ? $cidr3 : undef;

    eval {
	$result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is ($result, $expected, $name);
    }


    $test = "del_cidr $cidr1";
    $name = "$testid $test";
    $result = undef;
    $expected = undef;

    eval {
	$result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

    $test = "del_cidr $cidr3";
    $name = "$testid $test";
    $result = undef;
    $expected = undef;

    eval {
	$result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr3, $hostname);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

    $test = "del_cidr not exist $cidr1";
    $name = "$testid $test";
    $result = undef;
    $expected = undef;

    eval {
	$result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
    };

    if ($@) {
        is (undef, undef, $name);
    } elsif($ipam) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

    $test = "del_cidr outofrange $cidr_outofrange";
    $name = "$testid $test";
    $result = undef;
    $expected = undef;

    eval {
	$result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr_outofrange, $hostname);
    };

    if ($@) {
        is (undef, undef, $name);
    } else {
        fail("$name : $@");
    }

    $test = "find_next_free_cidr_in_first_subnet ($cidr1)";
    $name = "$testid $test";
    $result = undef;
    $expected = $ipam ? $cidr1 : undef;

    eval {
	$result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is ($result, $expected, $name);
    }

    $test = "update_cidr $cidr1";
    $name = "$testid $test";
    $result = undef;
    $expected = undef;

    eval {
	$result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

    $test = "update_cidr deleted $cidr3";
    $name = "$testid $test";
    $result = undef;
    $expected = undef;

    eval {
	$result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
    };

    if ($@) {
        fail("$name : $@");
    } else {
        is (undef, undef, $name);
    }

}

done_testing();


