package NGCP::Test::Resource;
use strict;
use warnings;

use Moose;
use Test::More;
use Test::Deep qw/cmp_bag/;
use Clone qw/clone/;
use Data::Dumper;
use JSON qw/from_json to_json/;
use NGCP::Test::Patch;

has '_test' => (
    isa => 'Object',
    is => 'ro',
);

has 'client' => (
    isa => 'Object',
    is => 'rw',
);

has 'resource' => (
    isa => 'Str',
    is => 'ro',
);

has 'data' => (
    isa => 'Maybe[HashRef]',
    is => 'ro',
);

has 'allowed' => (
    isa => 'HashRef',
    is => 'ro',
    default => sub { { collection => [], item => [] } },
);

has 'autodelete_created_items' => (
    isa => 'Bool',
    is => 'rw',
    default => 1,
);

has 'print_summary_on_finish' => (
    isa => 'Bool',
    is => 'rw',
    default => 0,
);

has 'test_count' => (
    isa => 'Int',
    is => 'rw',
    default => 0,
);

has 'created_items' => (
    isa => 'ArrayRef',
    is => 'rw',
    default => sub {[]}
);

has 'requests' => (
    is => 'rw',
    isa => 'ArrayRef',
    default => sub {[]},
);

sub BUILD {
    my $self = shift;
    unless($self->client) {
        die "Missing parameter 'client' when creating " . __PACKAGE__ . "\n";
    }
    unless($self->resource) {
        die "Missing parameter 'resource' when creating " . __PACKAGE__ . "\n";
    }
}

sub _process_input {
    my ($self, $args) = @_;

    my @data = ();
    my @qparams= ();
    my @expected = ();

    if(exists $args->{data_replace}) {
        my $ref = ref $args->{data_replace};
        if($ref eq 'HASH') {
            push @data, ($args->{data_replace});
        } elsif($ref eq 'ARRAY') {
            @data = @{ $args->{data_replace} };
        } else {
            die "Invalid 'data_replace' type, must be hashref or arrayref\n";
        }
    } else {
        @data = ($self->data);
    }
    if(exists $args->{expected_result}) {
        my $ref = ref $args->{expected_result};
        if($ref eq 'HASH') {
            @expected = map { $args->{expected_result} } (1 .. @data);
        } elsif($ref eq 'ARRAY') {
            @expected = @{ $args->{expected_result} };
            unless($#expected == $#data) {
                die "Number of elements in 'data_replace' doesn't match number of elements in 'expected_result'\n";
            }
        } else {
            die "Invalid 'data_replace' type, must be hashref or arrayref\n";
        }
    } else {
        die "Missing argument 'expected_result'\n";
    }
    if(exists $args->{query_params}) {
        my $ref = ref $args->{query_params};
        if($ref eq 'HASH') {
            push @qparams, ($args->{query_params});
        } elsif($ref eq 'ARRAY') {
            @qparams = @{ $args->{query_params} };
        } else {
            die "Invalid 'query_params' type, must be hashref or arrayref\n";
        }
    }

    return { data => \@data, expected => \@expected, qparams => \@qparams };
}

sub _get_replaced_data {
    my ($self, $data, $repl) = @_;

    my $allrepl = [];

    if(ref $repl eq "HASH") {
        push @{ $allrepl }, $repl;
    } elsif(ref $repl eq "ARRAY") {
        $allrepl = $repl;
    } else {
        die "All data_replace elements must be hashref or arrayref\n";
    }
    my $d = clone($data);
    foreach my $repl(@{ $allrepl }) {
        my $key = $repl->{field};
        my $val = $repl->{value};
        if(defined $key) {
            my @parts = split(/\./, $key);
            my $tmp = \$d;
            for(my $i = 0; $i < @parts; ++$i) {
                if($repl->{delete} && $i == @parts - 1) {
                    last;
                }
                $tmp = \$$$tmp{$parts[$i]};
            }
            if($repl->{delete}) {
                delete $$tmp->{$parts[$#parts]};
            } else {
                $$tmp = $val;
            }
        }
    }
    return $d;
}

sub _apply_patch {
    my ($self, $data, $json_patch) = @_;
   
    my $p = NGCP::Test::Patch->new;
    my $res = $p->apply_patch($data, $json_patch);
    return $res;

}

sub _get_patch_data {
    my ($self, $data, $repl) = @_;

    # TODO: support dot-style notation for nested fields

    my $key = $repl->{field};
    my $path = "/$key";
    $path =~ s/\./\//g;
    my $val = $repl->{value};
    my $op = $repl->{op};
    my $d = {
        op => $op,
        path => $path,
    };
    if($op eq "remove") {
        # no value to be set
    } elsif($op eq "copy" || $op eq "move") {
        # value is the "from" path
        my $from = "/$val";
        $from =~ s/\./\//g;
        $d->{from} = $from;
    } else {
        $d->{value} = $val;
    }
    $d = [$d];
    return $d;
}

sub _test_fields {
    my ($self, $skip, $expected, $ref, $item, $name) = @_;
    #print "Testing fields for $name\n";
    #print Dumper $ref;
    #print Dumper $item;

    $ref //= {};

    $skip //= [];
    my %skip = ();
    foreach my $s(@{ $skip }) {
        my @f = split /\./, $s;
        my $elem = \%skip;
        foreach my $p(@f) {
            $elem->{$p} = {};
            $elem = $elem->{$p};
        }
    }

    my %expect = map { $_ => 0 } @{ $expected // [] };
    foreach my $field(keys %{ $ref }) {
        next if($field eq "_links");

        if(defined $expected && exists $expect{$field}) {
            ok(exists $item->{$field}, "$name - expected field $field seen");
            $self->_inc_test_count;
            $expect{$field} = 1;
        }

        next if(exists $skip{$field} && !keys %{ $skip{$field} });

        if(defined $expected && !exists($expect{$field})) {
            next;
        }
        if(ref $ref->{$field} eq "ARRAY") {
            if(exists $skip{$field} && keys %{ $skip{$field} } && @{ $ref->{$field} } && ref $ref->{$field}->[0] eq "HASH") {
                for(my $i = 0; $i < @{ $ref->{$field} }; ++$i) {
                    my $refelem = $ref->{$field}->[$i];
                    my $elem = $item->{$field}->[$i];
                    my $k = (keys %{ $skip{$field} })[0]; 
                    my $refold = delete $refelem->{$k};
                    my $old = delete $elem->{$k};
                    $self->_test->debug("compare content of $field\n");
                    $self->_test->debug("element: " . Dumper $elem);
                    $self->_test->debug("reference element: " . Dumper $refelem);
                    is_deeply($elem, $refelem, $name . " - content of $field");
                    $self->_inc_test_count;
                    $refelem->{$k} = $refold;
                    $elem->{$k} = $old;
                }
                is(@{ $ref->{$field} }, @{ $item->{$field} }, "$name - element count of $field");
                $self->_inc_test_count;
            } else {
                $self->_test->debug("compare content of $field via bag comparison\n");
                $self->_test->debug("element: " . Dumper $item->{$field});
                $self->_test->debug("reference element: " . Dumper $ref->{$field});
                cmp_bag($item->{$field}, $ref->{$field}, $name . " - content of $field");
                $self->_inc_test_count;
            }
        } elsif(ref $ref->{$field} eq "HASH") {
            if(exists $skip{$field} && keys %{ $skip{$field} }) {
                my $k = (keys %{ $skip{$field} })[0]; 
                my $refold = delete $ref->{$field}->{$k};
                my $old = delete $item->{$field}->{$k};
                is_deeply($item->{$field}, $ref->{$field}, $name . " - content of $field");
                $self->_inc_test_count;
                $ref->{$field}->{$k} = $refold;
                $item->{$field}->{$k} = $old;
            } else {
                is_deeply($item->{$field}, $ref->{$field}, $name . " - content of $field");
                $self->_inc_test_count;
            }
        } elsif(ref $ref->{$field} ne "") {
            is_deeply($item->{$field}, $ref->{$field}, $name . " - content of $field");
            $self->_inc_test_count;
        } else {
            is($item->{$field}, $ref->{$field}, $name . " - content of $field");
            $self->_inc_test_count;
        }
    }
    if(defined $expected) {
        foreach my $field(keys %{ $item }) {
            next if($field eq "_links");
            ok(exists $expect{$field}, "$name - field $field expected");
            $self->_inc_test_count;
            delete $expect{$field};
        }
        foreach my $field(keys %expect) {
            delete $expect{$field} if($expect{$field} == 1);
        }
    }

    if(!ok(keys %expect == 0, "$name - all expected fields seen")) {
        diag("the following expected fields were not seen: " . join(', ', keys %expect));
        diag("existing fields were: " . join(', ', keys %{ $item }));
    }
    $self->_inc_test_count;
}

sub test_allowed_methods {
    my ($self, $item) = @_;

    # while at it, test without item as well
    if(defined $item) {
        $self->test_allowed_methods();
    }

    my $res;
    my $expected;
    my $name = $self->resource . ' - allowed methods';
    if(defined $item) {
        $res = $self->client->_options('api/'.$self->resource.'/'.$item->{id});
        $name .= " - item";
        $expected = $self->allowed->{item};
    } else {
        $res = $self->client->_options('api/'.$self->resource);
        $name .= " - collection";
        $expected = $self->allowed->{collection};
    }

    is($res->code, 200, "$name - options request");
    $self->_inc_test_count;

    return unless($res->is_success);
    my $data = from_json($res->decoded_content);
    my $methods = $data->{methods};

    my @hopts = split /\s*,\s*/, $res->header('Allow');

    is(@hopts, @{ $expected }, "$name - correct amount of allowed methods in header");
    $self->_inc_test_count;
    is(@{ $methods }, @{ $expected }, "$name - correct amount of allowed methods in body");
    $self->_inc_test_count;

    while((my $opt = shift @{ $expected })) {
        ok(grep { /^$opt$/ } @hopts, "$name - '$opt' in Allow header");
        $self->_inc_test_count;
        ok(grep { /^$opt$/ } @{ $methods }, "$name - '$opt' in body");
        $self->_inc_test_count;
    }
    is(@{ $expected }, 0, "$name - no leftover in allowed methods");
    $self->_inc_test_count;
}

sub test_empty_bodies {
    my ($self, $item) = @_;

    # while at it, test without item as well
    if(defined $item) {
        $self->test_empty_bodies();
    }

    my $res;
    my $name = $self->resource . ' - missing body';
    if(defined $item) {
        $name .= " - put";
        $res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, undef);
        is($res->code, 400, $name); 
        $self->_inc_test_count;

        $name .= " - patch";
        $res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, undef);
        is($res->code, 400, $name); 
        $self->_inc_test_count;
    } else {
        $name .= " - post";
        $res = $self->client->_post('api/'.$self->resource, undef);
        is($res->code, 400, $name); 
        $self->_inc_test_count;
    }
}

sub test_invalid_headers {
    my ($self, $item) = @_;

    # while at it, test without item as well
    if(defined $item) {
        $self->test_invalid_headers();
    }

    my $res;
    my $data;
    my $testname = $self->resource . ' - invalid headers';
    my $name;
    if(defined $item) {
        $name = "$testname - put without content-type";
        $res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, '', undef);
        is($res->code, 415, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Unsupported Media Type/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/'undefined', accepting application\/json only/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - put with invalid content-type";
        $res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, 'something/invalid', undef);
        is($res->code, 415, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Unsupported Media Type/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/'something\/invalid', accepting application\/json only/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - patch without content-type";
        $res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $item, '', undef);
        is($res->code, 415, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Unsupported Media Type/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/'undefined', accepting application\/json-patch\+json only/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - patch with invalid content-type";
        $res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $item, 'application/json', undef);
        is($res->code, 415, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Unsupported Media Type/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/'application\/json', accepting application\/json-patch\+json only/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - put without prefer";
        $res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, undef, '');
        is($res->code, 204, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/No Content/, "$name - status"); 
        $self->_inc_test_count;
        is($res->decoded_content, '', "$name - no content");
        $self->_inc_test_count;

        $name = "$testname - put with invalid prefer";
        $res = $self->client->_put('api/'.$self->resource.'/'.$item->{id}, $item, undef, 'something=invalid');
        is($res->code, 400, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Bad Request/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/Header 'Prefer' must be either 'return=minimal' or 'return=representation'/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - patch without prefer";
        $res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, [], undef, '');
        is($res->code, 204, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/No Content/, "$name - status"); 
        $self->_inc_test_count;
        is($res->decoded_content, '', "$name - no content");
        $self->_inc_test_count;

        $name = "$testname - patch with invalid prefer";
        $res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $item, undef, 'something=invalid');
        is($res->code, 400, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Bad Request/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/Header 'Prefer' must be either 'return=minimal' or 'return=representation'/, "$name - error");
        $self->_inc_test_count;

    } else {
        $item = { test => 'test' };
        $name = "$testname - post without content-type";
        $res = $self->client->_post('api/'.$self->resource, $item, '', undef);
        is($res->code, 415, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Unsupported Media Type/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/Unsupported media type 'undefined', accepting application\/json only/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - post with invalid content-type";
        $res = $self->client->_post('api/'.$self->resource, $item, 'something/invalid', undef);
        is($res->code, 415, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Unsupported Media Type/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/'something\/invalid', accepting application\/json only/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - post without prefer";
        $res = $self->client->_post('api/'.$self->resource, {}, undef, '');
        is($res->code, 422, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Unprocessable Entity/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        # we assume here that at least ANY field must be existent in a post
        like($data->{message}, qr/Validation failed/, "$name - error");
        $self->_inc_test_count;

        $name = "$testname - post with invalid prefer";
        $res = $self->client->_post('api/'.$self->resource, $item, undef, 'something=invalid');
        is($res->code, 400, "$name - code"); 
        $self->_inc_test_count;
        like($res->message, qr/Bad Request/, "$name - status"); 
        $self->_inc_test_count;
        $data = from_json($res->decoded_content);
        like($data->{message}, qr/Header 'Prefer' must be either 'return=minimal' or 'return=representation'/, "$name - error");
        $self->_inc_test_count;
    }
}

sub _test_link {
    my ($self, $href, $name) = @_;
    my $res = $self->client->_get($href);
    $self->_push_request($name, $res);
    return $res->is_success;
}

sub _test_item {
    my ($self, $item, $args, $name, $ref) = @_;

    ok(!exists $item->{_embedded}, "$name - absence of _embedded in item");
    $self->_inc_test_count;
    ok(!exists $item->{total_count}, "$name - absence of total_count in item");
    $self->_inc_test_count;
    $self->_test_fields($args->{skip_test_fields}, $args->{expected_fields}, $ref, $item, $name);
    my %links = map { $_ => 0 } @{ $args->{expected_links} };
    foreach my $link(keys %{ $item->{_links} }) {
        next if(grep { /^$link$/ } qw/self curies profile collection/);
        ok(exists $links{$link}, "$name - existence of link $link in reference");
        $self->_inc_test_count;
        delete $links{$link};
        ok($self->_test_link($item->{_links}->{$link}->{href}, $name), "$name - follow link to $link");
        $self->_inc_test_count;
    }
    if(!ok(keys %links == 0, "$name - missing links")) {
        diag("missing links: " . join(", ", keys %links));
    }
    $self->_inc_test_count;
}

sub test_get {
    my ($self, %args) = @_;
    my $testname = $self->resource . ' - ' . ($args{name} // 'test_get');
    my $ref = $args{item};
    my $input = $self->_process_input(\%args);
    unless(@{ $input->{qparams} }) {
        $input->{qparams} = [{}];
    }

    my @qparams = @{ $input->{qparams} };
    my @expected = @{ $input->{expected} };
    my @items = ();

    if($ref && @qparams != 1) {
        die "When providing a reference 'item', query_params must contain one row only\n";
    }

    for(my $i = 0; $i < @qparams; ++$i) {
        my $e = $expected[$i];
        my $q = $qparams[$i];
        my $name = "$testname $i";

        my $uri = 'api/'.$self->resource . ($ref ? "/$$ref{id}" : "");
        my @tmpq = ();
        foreach my $k(keys %{ $q }) {
            push @tmpq, "$k=$$q{$k}"; 
        }
        if(@tmpq) {
            $uri .= '?' . join('&', @tmpq);
        }

        my $res = $self->client->_get($uri);
        is($res->code, $e->{code}, $name . " - code");
        $self->_inc_test_count;
        if(exists $e->{reason_re}) {
            like($res->message, qr/$e->{reason_re}/, $name . " - message");
            $self->_inc_test_count;
        } elsif(exists $e->{error_re}) {
            ok(!$res->is_success, $name . " - is an error");
            $self->_inc_test_count;
            my $item = from_json($res->decoded_content);
            like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
            $self->_inc_test_count;
        }

        if($res->code =~ /^2\d\d$/) {
            my $item = from_json($res->decoded_content);
            push @items, $item;
            if($ref) {
                $self->_test_item($item, \%args, $name, $ref);
            } else {
                ok(exists $item->{total_count}, "$name - presence of total_count in collection");
                $self->_inc_test_count;
                if($item->{total_count}) {
                    ok(exists $item->{_embedded}, "$name - presence of _embedded in collection on data");
                    $self->_inc_test_count;
                }
                if(exists $args{expected_count}) {
                    is($item->{total_count}, $args{expected_count}, "$name - total_count value");
                    $self->_inc_test_count;
                }
                foreach my $subitem(@{ $item->{_embedded}->{"ngcp:".$self->resource} }) {
                    $self->_test_item($subitem, \%args, $name, undef);
                }
            }

        }
    }
    return \@items;
}


sub test_post {
    my ($self, %args) = @_;
    my $testname = $self->resource . ' - ' . ($args{name} // 'test_post');

    my $input = $self->_process_input(\%args);

    my @data = @{ $input->{data} };
    my @expected = @{ $input->{expected} };

    for(my $i = 0; $i < @data; ++$i) {

        my $d = $self->_get_replaced_data($self->data, $data[$i]);
        my $e = $expected[$i];
        my $name = "$testname $i";

        # fire request and test result
        my $res = $self->client->_post('api/'.$self->resource, $d);
        $self->_push_request($name, $res);
        is($res->code, $e->{code}, $name . " - code");
        $self->_inc_test_count;
        if(exists $e->{reason_re}) {
            like($res->message, qr/$e->{reason_re}/, $name . " - message");
            $self->_inc_test_count;
        } elsif(exists $e->{error_re}) {
            ok(!$res->is_success, $name . " - is an error");
            $self->_inc_test_count;
            my $item = from_json($res->decoded_content);
            like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
            $self->_inc_test_count;
        }
        # copy created item into cache
        if($res->code =~ /^2\d\d$/) {
            my $item;
            if($res->decoded_content) {
                # TODO: do we need to extract from JSON::HAL?
                $item = from_json($res->decoded_content);
            } else {
                my $id = $res->header('Location');
                $id =~ s/^.+\/([^\/]+)$/$1/;
                $res = $self->client->_get('api/'.$self->resource.'/'.$id);
                $self->_push_request($name, $res);
                unless($res->is_success) {
                    die "Failed to re-fetch created resource " . $self->resource . " with id $id\n";
                }
                # TODO: do we need to extract from JSON::HAL?
                $item = from_json($res->decoded_content);
            }
            $self->push_created_item($item);
            $self->_test_fields($args{skip_test_fields}, $args{expected_fields}, $d, $item, $name);
        }
    }
}

sub test_put {
    my ($self, %args) = @_;
    my $testname = $self->resource . ' - ' . ($args{name} // 'test_put');
    my $item = $args{item};

    my $input = $self->_process_input(\%args);

    my @data = @{ $input->{data} };
    my @expected = @{ $input->{expected} };

    my @items = ();

    for(my $i = 0; $i < @data; ++$i) {

        my $d = $self->_get_replaced_data($item, $data[$i]);
        $self->_test->debug("replaced data: " . Dumper $d);
        my $e = $expected[$i];
        my $name = "$testname $i";

        # fire request and test result
        my $res = $self->client->_put('api/'.$self->resource.'/'.$d->{id}, $d);
        $self->_push_request($name, $res);
        is($res->code, $e->{code}, $name . " - code");
        $self->_inc_test_count;
        if(exists $e->{reason_re}) {
            like($res->message, qr/$e->{reason_re}/, $name . " - message");
            $self->_inc_test_count;
        } elsif(exists $e->{error_re}) {
            ok(!$res->is_success, $name . " - is an error");
            $self->_inc_test_count;
            my $item = from_json($res->decoded_content);
            like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
            $self->_inc_test_count;
        }
        if($res->code =~ /^2\d\d$/ && $res->decoded_content) {
            # TODO: do we need to extract from JSON::HAL?
            my $resitem = from_json($res->decoded_content);
            push @items, $resitem;
            $self->_test_fields($args{skip_test_fields}, $args{expected_fields}, $d, $resitem, $name);
        } else {
            push @items, $item;
        }
    }
    return \@items;
}


sub test_patch {
    my ($self, %args) = @_;
    my $testname = $self->resource . ' - ' . ($args{name} // 'test_patch');
    my $item = $args{item};

    my $input = $self->_process_input(\%args);

    my @data = @{ $input->{data} };
    my @expected = @{ $input->{expected} };

    my @items = ();

    for(my $i = 0; $i < @data; ++$i) {

        my $d = $self->_get_patch_data($item, $data[$i]);
        my $refd = $self->_apply_patch($item, to_json($d));
        my $e = $expected[$i];
        my $name = "$testname $i";

        # fire request and test result
        my $res = $self->client->_patch('api/'.$self->resource.'/'.$item->{id}, $d);
        $self->_push_request($name, $res);
        is($res->code, $e->{code}, $name . " - code");
        $self->_inc_test_count;
        if(exists $e->{reason_re}) {
            like($res->message, qr/$e->{reason_re}/, $name . " - message");
            $self->_inc_test_count;
        } elsif(exists $e->{error_re}) {
            ok(!$res->is_success, $name . " - is an error");
            $self->_inc_test_count;
            my $item = from_json($res->decoded_content);
            like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
            $self->_inc_test_count;
        }
        if($res->code =~ /^2\d\d$/ && $res->decoded_content) {
            # TODO: do we need to extract from JSON::HAL?
            my $resitem = from_json($res->decoded_content);
            push @items, $resitem;
            $self->_test_fields($args{skip_test_fields}, $args{expected_fields}, $refd, $resitem, $name);
        } else {
            push @items, $item;
        }
    }
    return \@items;
}

sub test_delete {
    my ($self, %args) = @_;
    my $testname = $self->resource . ' - ' . ($args{name} // 'test_delete');
    my $item = $args{item};

    my $input = $self->_process_input(\%args);

    my @data = @{ $input->{data} };
    my @expected = @{ $input->{expected} };

    my @items = ();

    for(my $i = 0; $i < @data; ++$i) {

        my $d = $self->_get_replaced_data($item, $data[$i]);
        my $e = $expected[$i];
        my $name = "$testname $i";

        # fire request and test result
        my $res = $self->client->_delete('api/'.$self->resource.'/'.$d->{id});
        $self->_push_request($name, $res);
        is($res->code, $e->{code}, $name . " - code");
        $self->_inc_test_count;
        if(exists $e->{reason_re}) {
            like($res->message, qr/$e->{reason_re}/, $name . " - message");
            $self->_inc_test_count;
        } elsif(exists $e->{error_re}) {
            ok(!$res->is_success, $name . " - is an error");
            $self->_inc_test_count;
            my $item = from_json($res->decoded_content);
            like($item->{message}, qr/$e->{error_re}/, $name . " - error message");
            $self->_inc_test_count;
        }
    }
}

=pod
# TODO: not needed?
sub _update_created_item {
    my ($self, $item) = @_;
    for(my $i = 0; $i < $self->count_created_items(); ++$i) {
        if($self->created_items->[$i]->{id} eq $item->{id}) {
            print "found old item in cache\n";
            @{$self->created_items}[$i] = $item;
        }
    }
}
=cut

sub _inc_test_count {
    my ($self) = @_;
    $self->_test->inc_test_count();
}

sub push_created_item {
    my ($self, $item) = @_;
    push @{ $self->created_items }, $item;
}

sub pop_created_item {
    my ($self) = @_;
    my $item = pop @{ $self->created_items };
    return $item;
}

sub shift_created_item {
    my ($self) = @_;
    my $item = shift @{ $self->created_items };
    return $item;
}

sub count_created_items {
    my ($self) = @_;
    return 0 + @{ $self->created_items };
}

sub print_summary {
    my ($self) = @_;

    diag("Performed Requests:");
    my $i = 0;
    foreach my $r(@{ $self->requests }) {
        $i++;
        diag("$$r{testname}:");
        diag("$$r{method} $$r{uri}");
        diag("$$r{code} $$r{message}");
        diag("rtt: $$r{rtt}");
        diag("");
    }
    diag("Total: $i");
}

sub _push_request {
    my ($self, $name, $res) = @_;
    push @{ $self->requests }, {
        testname => $name,
        method => $res->request->method,
        uri => $res->request->uri,
        code => $res->code,
        message => $res->message,
        rtt => $self->client->last_rtt,
    };
}


sub DEMOLISH {
    my ($self) = @_;
    my @failed = ();
    my @threads = ();
    if($self->autodelete_created_items) {
        while((my $item = $self->pop_created_item())) {
            my $id = $item->{id};
            my $url = 'api/'.$self->resource.'/'.$id;
            my $res = $self->client->_delete($url);
            unless($res->is_success) {
                my $data = from_json($res->decoded_content);
                if($res->code == 404) {
			        $res = $self->client->_patch($url, [{
                        op => 'replace',
                        path => '/status',
                        value => 'terminated'
                    }]);
                    unless($res->is_success) {
                        diag("Failed to both auto-delete or auto-terminate '$url': $$data{message}");
                        push @failed, $id;
                    } else {
                        #print "The $url successfully terminated\n";
                    }
                }
            } else {
                #print "The $url successfully deleted\n";
            }

            $self->_push_request('autodelete', $res);
        }
        if(@failed) {
            diag("failed to auto-delete the following " . 
                $self->resource . ": " .  (join ', ', @failed));
        }
    }
    if($self->print_summary_on_finish) {
        $self->print_summary();
    }
}

1;

__END__

=head1 NAME

NGCP::Test::Resource

=head1 DESCRIPTION

NGCP Panel test resource

=head1 AUTHOR

Sipwise Development Team

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
