use Sipwise::Base;
use Net::Domain qw(hostfqdn);
use JSON qw();
use Test::More;
use DateTime::Format::ISO8601;
use Data::Dumper;
use DateTime;
use DateTime::TimeZone;
#use URI::Escape qw();
use warnings;

BEGIN {
    unshift(@INC,'../../lib');
}
use NGCP::Panel::Utils::DateTime qw();

my $is_local_env = 0;

my $uri = $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443');

my ($ua, $req, $res);

use Test::Collection;
$ua = Test::Collection->new()->ua();

#$ua->add_handler("request_send",  sub {
#    my ($request, $ua, $h) = @_;
#    print $request->method . ' ' . $request->uri . "\n" . ($request->content ? $request->content . "\n" : '') unless $request->header('authorization');
#    return undef;
#});
#$ua->add_handler("response_done", sub {
#    my ($response, $ua, $h) = @_;
#    print $response->decoded_content . "\n" if $response->code != 401;
#    return undef;
#});

my $t = time;

my %carrier_map = ();
my %number_map = ();

#goto SKIP;

{ #regular case
    my $carrier1 = _create_lnp_provider();
    my $number1 = _create_lnp_number($carrier1, number => '123'.$t);
    my $number2 = _create_lnp_number($carrier1, number => '456'.$t);
    _delete_lnp_number($number1);
    _delete_lnp_number($number2);
    _delete_lnp_provider($carrier1);
}

$t += 1;

{ #delete provider with numbers left:
    my $carrier1 = _create_lnp_provider();
    my $number1 = _create_lnp_number($carrier1, number => '123'.$t);
    _delete_lnp_provider($carrier1, 500);
}

$t += 1;

{
    my $carrier1 = _create_lnp_provider();
    my $carrier2 = _create_lnp_provider();
    my $number = '123'.$t;
    _set_time(NGCP::Panel::Utils::DateTime::from_string('2017-04-18 14:00:00'));
    my $now = NGCP::Panel::Utils::DateTime::current_local();
    my $start1 = $now->clone->subtract(days=>1);
    my $start2 = $now->clone->subtract(days=>2);
    my $end = $now->clone->add(days=>1);
    my $number1 = _create_lnp_number($carrier1, number => $number, start => $start1->ymd, end => $end->ymd);
    my $number2 = _create_lnp_number($carrier2, number => $number, start => $start2->ymd, end => $end->ymd);

    _check_lnpnumber_history("total history of number $number: ",$number,[
        { carrier_id => $carrier2->{id}, start => $start2->ymd, end => $end->ymd },
        { carrier_id => $carrier1->{id}, start => $start1->ymd, end => $end->ymd },
    ],undef);

    _check_lnpnumber_history("actual number $number before it was registered at all: ",$number,[
    ],$now->clone->subtract(days=>3));

    _check_lnpnumber_history("actual number $number after registration for first carrier: ",$number,[
        { carrier_id => $carrier1->{id}, },
    ],$now->clone->subtract(days=>1)->add(hours=>1));

    _check_lnpnumber_history("actual number $number after registration for second carrier: ",$number,[
        { carrier_id => $carrier2->{id}, },
    ],$now->clone->subtract(days=>2)->add(hours=>1));

    _check_lnpnumber_history("actual number $number now: ",$number,[
        { carrier_id => $carrier1->{id}, },
    ],"");

    _check_lnpnumber_history("actual number $number beyond/'terminated': ",$number,[
    ],$end->clone->add(days => 1)); #23:59 vs 00:00

    #_delete_lnp_number($number1);
    #_delete_lnp_number($number2);
    #_delete_lnp_provider($carrier1);
    #_delete_lnp_provider($carrier2);

    _set_time();
}

$t += 1;
#SKIP:
{
    my $carrier1 = _create_lnp_provider();
    my $carrier2 = _create_lnp_provider();
    my $number = '123'.$t;

    _set_time(NGCP::Panel::Utils::DateTime::from_string('2017-04-18 14:00:00'));
    my $now = NGCP::Panel::Utils::DateTime::current_local();
    my $start1 = $now->clone->subtract(days=>2);
    my $start2 = $now->clone->subtract(days=>1);
    my $end = $now->clone->add(days=>5);

    _terminate_lnp_numbers(undef,500); #empty number

    _terminate_lnp_numbers($number,404); #not found, all records

    my $number1 = _create_lnp_number($carrier1, number => $number, start => $start1->ymd, end => $end->ymd);
    my $number2 = _create_lnp_number($carrier2, number => $number, start => $start2->ymd, end => $end->ymd);

    _check_lnpnumber_history("number $number: ",$number,[
        { carrier_id => $carrier1->{id} },
        { carrier_id => $carrier2->{id} },
    ],undef);

    _terminate_lnp_numbers($number,404,$now->clone->add(days=>6)); #not found, actual

    _terminate_lnp_numbers($number,undef,$start1->clone->add(hours=>1)); #delete actual

    _check_lnpnumber_history("number $number: ",$number,[
        { carrier_id => $carrier1->{id}, end => $start1->ymd },
        { carrier_id => $carrier2->{id}, end => $end->ymd },
    ],undef);

    _terminate_lnp_numbers($number); #delete all

    sleep(2);;
    #$now = DateTime->now();
    #$now->set_time_zone( DateTime::TimeZone->new(name => 'local') );
    _check_lnpnumber_history("number $number: ",$number,[
    ],"");

    _check_lnpnumber_history("number $number: ",$number,[
        { carrier_id => $carrier1->{id}, end => $start1->ymd  },
        { carrier_id => $carrier2->{id}, end => $now->ymd },
    ],undef);

    #_delete_lnp_number($number1);
    #_delete_lnp_number($number2);
    #_delete_lnp_provider($carrier1);
    #_delete_lnp_provider($carrier2);

    _set_time();
}

$t += 1;

{ #unique number - insert:
    my $carrier1 = _create_lnp_provider();
    my $number1 = _create_lnp_number($carrier1, number => '123'.$t);
    my $number2 = _create_lnp_number($carrier1, number => '123'.$t, expected_code => 201); # allowed again
    #my $number2 = _create_lnp_number($carrier1, number => '123'.$t, expected_code => 422);
    #_delete_lnp_provider($carrier1, expected_code => 500);
}

$t += 1;

{ #unique number - update:
    my $carrier1 = _create_lnp_provider();
    my $number1 = _create_lnp_number($carrier1, number => '123'.$t);
    my $number2 = _create_lnp_number($carrier1, number => '1234'.$t);
    _update_lnp_number($number2, number => '123'.$t, expected_code => 200); # allowed again
    #_update_lnp_number($number2, number => '123'.$t, expected_code => 422);
}

sub _create_lnp_number {

    my $carrier = shift;
    my (%further_opts) = @_;
    my $expected_code = delete $further_opts{expected_code} // 201;
    $req = HTTP::Request->new('POST', $uri.'/api/lnpnumbers/');
    $req->header('Content-Type' => 'application/json');
    $req->header('X-Fake-Clienttime' => _get_fake_clienttime_now());
    $req->content(JSON::to_json({
        carrier_id => $carrier->{id},
        number => 'test'.$t,
        %further_opts,
    }));
    $res = $ua->request($req);
    if ($expected_code eq '201') {
        is($res->code, 201, "create test lnp number");
        $req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
        $res = $ua->request($req);
        is($res->code, 200, "fetch test lnp number");
        my $number = JSON::from_json($res->decoded_content);
        $number_map{$carrier->{id}} = $number;
        return $number;
    } else {
        is($res->code, $expected_code, "create test lnp number returns $expected_code");
        return;
    }

}

sub _update_lnp_number {

    my $number = shift;
    my (%further_opts) = @_;
    my $expected_code = delete $further_opts{expected_code} // 200;
    my $url = $uri.'/api/lnpnumbers/'.$number->{id};
    $req = HTTP::Request->new('PUT', $url);
    $req->header('Content-Type' => 'application/json');
    $req->header('X-Fake-Clienttime' => _get_fake_clienttime_now());
    $req->header('Prefer' => 'return=representation');
    $req->content(JSON::to_json({
        %$number,  ## no critic (ProhibitCommaSeparatedStatements)
        %further_opts,
    }));
    $res = $ua->request($req);
    if ($expected_code eq '200') {
        is($res->code, 200, "update test lnp number");
        $number = JSON::from_json($res->decoded_content);
        $number_map{$number->{id}} = $number;
        return $number;
    } else {
        is($res->code, $expected_code, "update test lnp number returns $expected_code");
        $req = HTTP::Request->new('GET', $url);
        $res = $ua->request($req);
        is($res->code, 200, "fetch test lnp number");
        my $got_number = JSON::from_json($res->decoded_content);
        is_deeply($got_number,$number,"lnp number unchanged");
        return;
    }
}

sub _delete_lnp_number {

    my ($number,$expected_code) = @_;
    $expected_code //= 204;
    my $url = $uri.'/api/lnpnumbers/'.$number->{id};
    $req = HTTP::Request->new('DELETE', $url);
    $req->header('X-Fake-Clienttime' => _get_fake_clienttime_now());
    $res = $ua->request($req);
    if ($expected_code eq '204') {
        is($res->code, 204, "delete test lnp number");
        $req = HTTP::Request->new('GET', $url);
        $req->header('X-Fake-Clienttime' => _get_fake_clienttime_now());
        $res = $ua->request($req);
        is($res->code, 404, "test lnp number is not found");
        return delete $number_map{$number->{id}};
    } else {
        is($res->code, $expected_code, "delete test lnp number returns $expected_code");
        $req = HTTP::Request->new('GET', $url);
        $req->header('X-Fake-Clienttime' => _get_fake_clienttime_now());
        $res = $ua->request($req);
        is($res->code, 200, "test lnp number is still found");
        return;
    }

}

sub _terminate_lnp_numbers {

    my ($number,$expected_code,$actual) = @_;
    $expected_code //= 204;
    my %params = ();
    if (defined $actual) {
        my $actual_delete = $actual;
        eval { $actual_delete = DateTime::Format::ISO8601->parse_datetime($actual); };
        $params{actual} = $actual_delete;
    }
    if (defined $number) {
        $params{number} = $number;
    }
    my $url = $uri.'/api/lnpnumbers/'._get_query_string(\%params);
    $req = HTTP::Request->new('DELETE', $url);
    $req->header('X-Fake-Clienttime' => _get_fake_clienttime_now());
    $res = $ua->request($req);
    if ($expected_code eq '204') {
        is($res->code, 204, "delete test lnp numbers");
        #_check_lnpnumber_history("test lnp numbers are not found: ",$number,[],$actual);
    } else {
        is($res->code, $expected_code, "delete test lnp numbers returns $expected_code");
    }

}

sub _create_lnp_provider {

    my (%further_opts) = @_;
    my $expected_code = delete $further_opts{expected_code} // 201;
    $req = HTTP::Request->new('POST', $uri.'/api/lnpcarriers/');
    $req->header('Content-Type' => 'application/json');
    $req->content(JSON::to_json({
        #skip_rewrite => JSON::false,
        name => "test_lnp_carrier_".(scalar keys %carrier_map).'_'.$t,
        prefix => 'test'.$t,
        %further_opts,
    }));
    $res = $ua->request($req);
    if ($expected_code eq '201') {
        is($res->code, 201, "create test lnp carrier");
        $req = HTTP::Request->new('GET', $uri.'/'.$res->header('Location'));
        $res = $ua->request($req);
        is($res->code, 200, "fetch test lnp carrier");
        my $carrier = JSON::from_json($res->decoded_content);
        $carrier_map{$carrier->{id}} = $carrier;
        return $carrier;
    } else {
        is($res->code, $expected_code, "create test lnp carrier returns $expected_code");
        return;
    }

}

sub _delete_lnp_provider {

    my ($carrier,$expected_code) = @_;
    $expected_code //= 204;
    my $url = $uri.'/api/lnpcarriers/'.$carrier->{id};
    $req = HTTP::Request->new('DELETE', $url);
    $res = $ua->request($req);
    if ($expected_code eq '204') {
        is($res->code, 204, "delete test lnp carrier");
        $req = HTTP::Request->new('GET', $url);
        $res = $ua->request($req);
        is($res->code, 404, "test lnp carrier is not found");
        return delete $carrier_map{$carrier->{id}};
    } else {
        is($res->code, $expected_code, "delete test lnp carrier returns $expected_code");
        $req = HTTP::Request->new('GET', $url);
        $res = $ua->request($req);
        is($res->code, 200, "test lnp carrier is still found");
        return;
    }

}

sub _check_lnpnumber_history {

    my ($label,$number,$expected_lnpnumbers,$actual) = @_;
    if (defined $actual) {
        eval { $actual = DateTime::Format::ISO8601->parse_datetime($actual); };
        $actual = '&actual=' . $actual;
    } else {
        $actual = '';
    }
    if (defined $number) {
        $number = '&number=' . $number;
    } else {
        $number = '';
    }

    my $total_count = (scalar @$expected_lnpnumbers);
    my $i = 0;
    my $ok = 1;
    my @lnpnumbers = ();
    my @requests = ();
    my $last_request;
    $last_request = _req_to_debug($req) if $req;
    my $nexturi = $uri.'/api/lnpnumbers/?page=1&rows=10&order_by_direction=asc&order_by=start'.$actual.$number;
    do {
        $req = HTTP::Request->new('GET',$nexturi);
        $req->header('X-Fake-Clienttime' => _get_fake_clienttime_now());
        $res = $ua->request($req);
        is($res->code, 200, $label . "fetch lnpnumbers collection page");
        push(@requests,_req_to_debug($req));
        my $collection = JSON::from_json($res->decoded_content);
        my $selfuri = $uri . $collection->{_links}->{self}->{href};
        my $colluri = URI->new($selfuri);

        $ok = ok($collection->{total_count} == $total_count, $label . "check 'total_count' of collection") && $ok;

        #my %q = $colluri->query_form;
        #ok(exists $q{page} && exists $q{rows}, $label . "check existence of 'page' and 'row' in 'self'");
        #my $page = int($q{page});
        #my $rows = int($q{rows});
        #if($page == 1) {
        #    ok(!exists $collection->{_links}->{prev}->{href}, $label . "check absence of 'prev' on first page");
        #} else {
        #    ok(exists $collection->{_links}->{prev}->{href}, $label . "check existence of 'prev'");
        #}
        #if(($collection->{total_count} / $rows) <= $page) {
        #    ok(!exists $collection->{_links}->{next}->{href}, $label . "check absence of 'next' on last page");
        #} else {
        #    ok(exists $collection->{_links}->{next}->{href}, $label . "check existence of 'next'");
        #}

        if($collection->{_links}->{next}->{href}) {
            $nexturi = $uri . $collection->{_links}->{next}->{href};
        } else {
            $nexturi = undef;
        }

        # TODO: I'd expect that to be an array ref in any case!
        #ok(ref $collection->{_links}->{'ngcp:lnpnumbers'} eq "ARRAY", $label . "check if 'ngcp:lnpnumbers' is array");

        $collection->{_embedded}->{'ngcp:lnpnumbers'} = [
            $collection->{_embedded}->{'ngcp:lnpnumbers'}
        ] if "HASH" eq ref $collection->{_embedded}->{'ngcp:lnpnumbers'};

        my $page_items = {};

        foreach my $lnpnumber (@{ $collection->{_embedded}->{'ngcp:lnpnumbers'} }) {
            #ok(exists $page_items->{$interval->{id}},$label . "check existence of linked item among embedded");
            #my $fetched = delete $page_items->{$interval->{id}};
            #delete $fetched->{content};
            #is_deeply($interval,$fetched,$label . "compare fetched and embedded item deeply");


            $ok = _compare_lnpnumber($lnpnumber,$expected_lnpnumbers->[$i],$label) && $ok;
            delete $lnpnumber->{'_links'};
            push(@lnpnumbers,$lnpnumber);
            $i++
        }

    } while($nexturi);

    ok($i == $total_count,$label . "check if all expected items are listed");
    diag(Dumper({last_request => $last_request, collection_requests => \@requests, result => \@lnpnumbers})) if !$ok;

}

sub _compare_lnpnumber {

    my ($got,$expected,$label) = @_;

    my $ok = 1;

    if ($expected->{id}) {
        $ok = is($got->{id},$expected->{id},$label . "check lnpnumber " . $got->{id} . " id") && $ok;
    }

    if ($expected->{start}) {
        $ok = is($got->{start},$expected->{start},$label . "check lnpnumber " . $got->{id} . " start date") && $ok;
    }

    if ($expected->{end}) {
        $ok = is($got->{end},$expected->{end},$label . "check lnpnumber " . $got->{id} . " end date") && $ok;
    }

    if ($expected->{number}) {
        $ok = is($got->{number},$expected->{number},$label . "check lnpnumber " . $got->{id} . " number") && $ok;
    }

    if ($expected->{carrier_id}) {
        $ok = is($got->{carrier_id},$expected->{carrier_id},$label . "check lnpnumber " . $got->{id} . " lnp_provider_id") && $ok;
    }

    return $ok;

}

sub _req_to_debug {
    my $request = shift;
    return { request => $request->method . " " . $request->uri,
            headers => $request->headers };
}

sub _get_query_string {
    my ($filters) = @_;
    my $query = '';
    foreach my $param (keys %$filters) {
        if (length($query) == 0) {
            $query .= '?';
        } else {
            $query .= '&';
        }
        #$query .= URI::Escape::uri_escape($param) . '=' . URI::Escape::uri_escape($filters->{$param});
        $query .= $param . '=' . $filters->{$param};
    }
    return $query;
};

sub _set_time {
    my ($o) = @_;
    my $dtf = DateTime::Format::Strptime->new(
            pattern => '%F %T',
        );
    if (defined $o) {
        NGCP::Panel::Utils::DateTime::set_fake_time($o);
        my $now = NGCP::Panel::Utils::DateTime::current_local;
        diag("applying fake time offset '$o' - current time: " . $dtf->format_datetime($now));
    } else {
        NGCP::Panel::Utils::DateTime::set_fake_time();
        my $now = NGCP::Panel::Utils::DateTime::current_local;
        diag("resetting fake time - current time: " . $dtf->format_datetime($now));
    }
}

sub _get_fake_clienttime_now {
    #return NGCP::Panel::Utils::DateTime::to_rfc1123_string(NGCP::Panel::Utils::DateTime::current_local);
    #with rfc1123 there could be a problem if jenkins runner and test host will not have the same (language) locale
    return NGCP::Panel::Utils::DateTime::to_string(NGCP::Panel::Utils::DateTime::current_local);
}

done_testing;
