package NGCP::API::TestFramework::TestExecutor;

use strict;
use warnings;
use Cpanel::JSON::XS;
use Data::Walk;
use Log::Log4perl qw(:easy);
use Moose;
use Test::More;
use Data::Dumper;

Test::More->builder->output ('/results/result.txt');
Test::More->builder->failure_output ('/results/errors.txt');

sub run_tests {
    my ( $self, $conditions, $result, $retained, $test_name ) = @_;

    my $tests_result = { success => 1 };
    foreach my $condition ( keys %$conditions ) {
        if ( $condition eq 'is' ) {
            while ( my ( $check_param, $check_value ) = each %{$conditions->{$condition}} ) {
                if ( $check_value =~ /^\$\{(.*)\}$/ ) {
                    $check_value = $retained->{$1};
                }
                if ( $check_param =~ /^\$\{(.*)\}$/ ) {
                    $check_param = $retained->{$1};
                }
                if ( $check_param =~ /.+\..+/ ) {
                    my @splitted_values = split (/\./, $check_param);
                    $check_param = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
                    DEBUG ( "Checking is for $check_param and $check_value" );
                    if ( is ($check_param, $check_value, $test_name) ) {
                        INFO ( "Check ok." );
                    }
                    else {
                        ERROR ( "NOT OK. Expected: $check_param. Got: $check_value" );
                        $tests_result->{success} = 0;
                        push @{$tests_result->{errors}}, "Error at 'is' condition for test '$test_name'";
                    }
                }
                elsif ( $check_param eq 'code' ) {
                    DEBUG ( "Checking is for ".$result->code." and $check_value" );
                    if ( is ($result->code, $check_value, $test_name) ) {
                        INFO ( "Check ok." );
                    }
                    else {
                        ERROR ( "NOT OK. Expected: $check_value. Got: ".$result->code);
                        $tests_result->{success} = 0;
                        push @{$tests_result->{errors}}, "Error at 'is' condition for test '$test_name'";
                    }
                }
                elsif ( $check_param eq 'header' ) {
                    foreach my $header_condition ( keys %{$conditions->{$condition}->{$check_param}} ) {
                        DEBUG ( "Checking is for ".$result->header($header_condition)."and ".$check_value );
                        if ( is ($result->header($header_condition), $check_value->{$header_condition}, $test_name) ) {
                            INFO ( "Check ok." );
                        }
                        else{
                            ERROR ( "NOT OK. Expected: ".$result->header($header_condition).". Got: ".$check_value->{$header_condition} );
                            $tests_result->{success} = 0;
                            push @{$tests_result->{errors}}, "Error at 'is' condition for test '$test_name'";
                        }
                    }
                }
                else {
                    DEBUG ( "Checking is for $check_param and $check_value" );
                    if ( is ($check_param, $check_value, $test_name) ) {
                        INFO ( "Check ok." );
                    }
                    else {
                        ERROR ( "NOT OK. Expected: ".$check_param.". Got: ".$check_value );
                        $tests_result->{success} = 0;
                        push @{$tests_result->{errors}}, "Error at 'is' condition for test '$test_name'";
                    }
                }
            }
        }
        elsif ( $condition eq 'ok' ) {
            foreach my $check_param (keys %{$conditions->{$condition}}) {
                if ( $check_param eq 'options' ) {
                    my $body = decode_json($result->decoded_content);
                    my @hopts = split /\s*,\s*/, $result->header('Allow');
                    DEBUG ( "Checking ok for options" );
                    if ( ok(exists $body->{methods} && ref $body->{methods} eq "ARRAY", $test_name) ) {
                        INFO ( "Check ok." );
                    }
                    else {
                        ERROR ( "NOT OK. Check failed for existence of methods in body and reference of \$body->{methods} is ARRAY");
                        $tests_result->{success} = 0;
                        push @{$tests_result->{errors}}, "Error at 'ok' condition for test '$test_name'";
                    }
                    foreach my $opt(@{$conditions->{$condition}->{$check_param}}) {
                        if ( ok(grep { /^$opt$/ } @hopts, $test_name) ) {
                            INFO ( "Check ok." );
                        }
                        else {
                            ERROR ( "NOT OK. Check failed for existence of ".$opt." in header methods" );
                            $tests_result->{success} = 0;
                            push @{$tests_result->{errors}}, "Error at 'ok' condition for test '$test_name'";
                        }
                        if ( ok(grep { /^$opt$/ } @{ $body->{methods} }, $test_name) ) {
                            INFO ( "Check ok." );
                        }
                        else {
                            ERROR ( "NOT OK. Check failed for existence of ".$opt." in body methods" );
                            $tests_result->{success} = 0;
                            push @{$tests_result->{errors}}, "Error at 'ok' condition for test '$test_name'";
                        }
                    }
                }
                if ( $conditions->{$condition}->{$check_param} eq 'defined' || $conditions->{$condition}->{$check_param} eq 'undefined') {
                    if ( $check_param =~ /.+\..+/ ) {
                        my @splitted_values = split (/\./, $check_param);
                        my $check_value = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
                        # if ($check_value eq 'Not found') {
                        #     push @{$tests_result->{warnings}}, "Warning: Required variable not found for test: $test_name.\n";
                        #     return $tests_result;
                        # }
                        if ( $conditions->{$condition}->{$check_param} eq 'defined' ) {
                            DEBUG ( "Checking ok for defined" );
                            if ( ok(defined $check_value, $test_name) ) {
                                INFO ( "Check ok." );
                            }
                            else {
                                ERROR ( "NOT OK. Check failed for existence of ".$check_param );
                                $tests_result->{success} = 0;
                                push @{$tests_result->{errors}}, "Error at 'ok' condition for test '$test_name'";
                            }
                        }
                        else {
                            DEBUG ( "Checking ok for undefined" );
                            if ( ok(!defined $check_value, $test_name) ) {
                                INFO ( "Check ok." );
                            }
                            else {
                                ERROR ( "NOT OK. Check failed for non-existence of ".$check_param );
                                $tests_result->{success} = 0;
                                push @{$tests_result->{errors}}, "Error at 'ok' condition for test '$test_name'";
                            }
                        }
                    }
                }
            }
        }
        elsif ( $condition eq 'like' ) {
            foreach my $check_param (keys %{$conditions->{$condition}}) {
                if ( $check_param =~ /.+\..+/ ) {
                    my @splitted_values = split (/\./, $check_param);
                    my $check_value = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
                    DEBUG ( "Checking like for ".$check_value." against ".$conditions->{$condition}->{$check_param} );
                    if ( like ($check_value, qr/$conditions->{$condition}->{$check_param}/, $test_name) ) {
                        INFO ( "Check ok." );
                    }
                    else{
                        ERROR ( "NOT OK. Expected: ".$check_value." to be like: ".$conditions->{$condition}->{$check_param} );
                        $tests_result->{success} = 0;
                        push @{$tests_result->{errors}}, "Error at 'like' condition for test '$test_name'";
                    }
                }
            }
        }
        elsif ( $condition eq 'is_deeply' ) {
            foreach my $check_param (keys %{$conditions->{$condition}}) {
                local *replace_variables = sub {
                    if ( ref $_ eq 'HASH' ) {
                        while ( my ( $key, $value ) = each %$_ ) {
                            if ( $value && $value =~ /\$\{(.*)\}/ ) {
                                $_->{$key} = $retained->{$1};
                            }
                        }
                    }
                };

                walkdepth {wanted => \&replace_variables}, $conditions->{$condition}->{$check_param};
                if ( $conditions->{$condition}->{$check_param} =~ /^\$\{(.*)\}$/ ) {
                    $conditions->{$condition}->{$check_param} = $retained->{$1};
                }
                my $check_value;
                if ( $check_param=~ /^\$\{(.*)\}$/ ) {
                    $check_value = $retained->{$1};
                }
                if ( $check_param =~ /.+\..+/ ) {
                    my @splitted_values = split (/\./, $check_param);
                    $check_value = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
                }
                DEBUG ( "Checking is_deeply for: ".(Dumper $check_value)." and ".(Dumper $conditions->{$condition}->{$check_param}) );
                if ( is_deeply ($check_value, $conditions->{$condition}->{$check_param}, $test_name) ) {
                    INFO ( "Check ok." );
                }
                else{
                    ERROR ( "NOT OK. Expected:\n".$check_value."\n. Got:\n".$conditions->{$condition}->{$check_param} );
                    $tests_result->{success} = 0;
                    push @{$tests_result->{errors}}, "Error at 'is_deeply' condition for test '$test_name'";
                }
            }
        }
    }
    return $tests_result;
}

sub _retrieve_from_composed_key {
    my ( $self, $result, $splitted_values, $retained ) = @_;

    if ( $splitted_values->[0] eq 'body' ) {
        my $body = decode_json($result->decoded_content);
        return $body->{$splitted_values->[1]};
    }
    elsif ( $splitted_values->[0] =~ /\$\{(.*)\}/ ) {
        if ( !defined $retained->{$1} ) {
            return 'Not found';
        }
        my $value = $retained->{$1};
        grep { $value = $value->{$splitted_values->[$_]} } (1..(scalar @$splitted_values - 1));
        return $value;
    }
}

1;
