use 5.014;
use File::Slurp qw(slurp);
use File::Temp;
use Test::Most;
use Test::JSON;
use Mojo::JSON;
use Mojo::Message::Request;
use Mojo::Message::Response;
use Mojo::UserAgent::Mockable::Serializer;
use Mojo::UserAgent::Mockable::Request::Compare;
use Mojolicious::Quick;

my $serializer = Mojo::UserAgent::Mockable::Serializer->new;

subtest 'Victoria and Albert Museum' => sub {
    my $dir = File::Temp->newdir;
    my $output_file = qq{$dir/victoria_and_albert.json};

    my @transactions;
    push @transactions, Mojo::UserAgent->new->get(q{http://www.vam.ac.uk/api/json/museumobject/?limit=1});

    my $result = $transactions[0]->res->json;

    plan skip_all => 'Museum API not responding properly' unless ref $result eq 'HASH' && $result->{'meta'};
    plan skip_all => 'No records returned' unless @{$result->{'records'}};

    my $object_number = $result->{'records'}[0]{'fields'}{'object_number'};

    push @transactions, Mojo::UserAgent->new->get(qq{http://www.vam.ac.uk/api/json/museumobject/$object_number}); 
    my $museum_object = $transactions[1]->res->json;

    plan skip_all => 'Museum object not retrieved properly' unless @{$museum_object} && keys %{$museum_object->[0]};

    test_transactions($output_file, @transactions);
};

subtest 'Local App' => sub {
    my $dir = File::Temp->newdir;
    my $output_file = qq{$dir/local_app.json};

    my $app = get_local_app();
    my $ua = Mojo::UserAgent->new;
    $ua->server->app($app);

    my $url = $ua->server->url->clone->path('/records');
    my @transactions = $ua->get($url);

    my $records = $transactions[0]->res->json;
    my $record_id = $records->{'records'}[0]{'id'};
    push @transactions,  $ua->get($url->clone->path(qq{/record/$record_id}));
    my $record = $transactions[1]->res->json;

    BAIL_OUT('Local app did not serve records correctly') unless $transactions[1]->res->json->[0]{'author'} eq 'Tommy Tutone';

    test_transactions($output_file, @transactions);
};

subtest 'random.org' => sub { 
    my $dir = File::Temp->newdir;
    my $output_file = qq{$dir/random_org.json};

    my $url = Mojo::URL->new( q{https://www.random.org/integers/} )->query(
        num    => 5,
        min    => 0,
        max    => 1e9,
        col    => 1,
        base   => 10,
        format => 'plain',
    );

    my $ua = Mojo::UserAgent->new;
    my @transactions = ($ua->get($url), $ua->get($url));

    test_transactions($output_file, @transactions);
};

subtest 'URL bits' => sub {
    my $dir = File::Temp->newdir;
    my $output_file = qq{$dir/local_random.json};

    my $app = get_local_random_app();
    my $ua = Mojo::UserAgent->new;
    $ua->server->app($app);

    my $url = Mojo::URL->new( $ua->server->url('https')->clone->path('/integers') )->query(
        num    => 5,
        min    => 0,
        max    => 1e9,
        col    => 1,
        base   => 10,
        format => 'plain',
    )->userinfo('nobody:nohow');

    my @transactions = ($ua->get($url), $ua->get($url));
    test_transactions($output_file, @transactions);
};

done_testing;

sub test_transactions {
    my ($output_file, @transactions) = @_;

    lives_ok { $serializer->store($output_file, @transactions) } q{serialize() did not die};

    my $serialized = slurp $output_file;
    is_valid_json($serialized, q{Serializer outputs valid JSON});

    my $decoded = Mojo::JSON::decode_json($serialized);

    is ref $decoded, 'ARRAY', q{Transactions serialized as array};
    for (0 .. $#transactions) {
        for my $key (qw/request response/) {
            ok defined($decoded->[$_]{$key}), qq{Key "$key" defined in serial data};
            for my $subkey (qw/class body/) {
                ok defined($decoded->[$_]{$key}{$subkey}), qq{Key "$subkey" defined in "$key" data};
            }
            if ($key eq 'request') {
                ok defined($decoded->[$_]{$key}{'url'}), qq{Key "url" defined in "$key" data};
            }
            my $expected_class = sprintf 'Mojo::Message::%s', ucfirst $key;
            is $decoded->[$_]{$key}{'class'}, $expected_class, qq{"$key" class correct};
        }
    }

    my @deserialized = $serializer->retrieve($output_file);
    for (0 .. $#transactions) {
        my $deserialized_tx = $deserialized[$_];
        my $tx = $transactions[$_];

        my $comparator = Mojo::UserAgent::Mockable::Request::Compare->new;
        if (!ok $comparator->compare($deserialized_tx->req, $tx->req), q{Serialized request matches original}) {
            diag q{Request mismatch: } . $comparator->compare_result;
        }

        is_deeply($deserialized_tx->res->headers->to_hash, $tx->res->headers->to_hash, q{Response headers match});

        is_deeply $deserialized_tx->res->json, $tx->res->json, q{Response encoded correctly};
    }

    return;
}

sub get_local_app {
    my $app = Mojolicious->new;
    $app->routes->get(
        '/records' => sub {
            my $c = shift;
            $c->render(
                json => {
                    meta    => { count => 1, },
                    records => [
                        {   id            => 8675309,
                            author        => 'Tommy Tutone',
                            subject       => 'Jenny',
                            repercussions => 'Many telephone companies now refuse to give out the number '
                                . '"867-5309".  People named "Jenny" have come to despise this song. '
                                . 'Mr. Tutone made out well.',
                        }
                    ],
                }
            );
        },
    );
    $app->routes->get(
        '/record/:id' => sub {
            my $c  = shift;
            my $id = $c->stash('id');
            if ( $id eq '8675309' ) {
                $c->render(
                    json => [
                        {   id            => 8675309,
                            author        => 'Tommy Tutone',
                            subject       => 'Jenny',
                            repercussions => 'Many telephone companies now refuse to give out the number '
                                . '"867-5309".  People named "Jenny" have come to despise this song. '
                                . 'Mr. Tutone made out well.',
                            summary => 'The singer wonders who he can turn to, and recalls Jenny, who he feels '
                                . 'gives him something that he can hold on to.  He worries that she will '
                                . 'think that he is like other men who have seen her name and number written '
                                . 'upon the wall, but persists in calling her anyway. In his heart, the '
                                . 'singer knows that Jenny is the girl for him.',
                        }
                    ]
                );
            }
        },
    );
    return $app;
}

sub get_local_random_app {
    my $app = Mojolicious->new;
    $app->routes->get(
        '/integers' => sub {
            my $c     = shift;
            my $count = $c->req->param('num') || 1;
            my $min   = $c->req->param('min') || 0;
            my $max   = $c->req->param('max') || 1e9;
            my $cols  = $c->req->param('cols') || 1;

            my @nums;
            for ( 0 .. ( $count - 1 ) ) {
                my $number = ( int rand( $max - $min ) ) + $min;
                push @nums, $number;
            }

            $c->render( text => join qq{\n}, @nums );
        },
    );
    return $app;
}
__END__
