use 5.008000;
use strict;
use warnings;
use utf8;

use Test::More;
use AnyEvent::Redis::RipeRedis qw( :err_codes );
require 't/test_helper.pl';

my $SERVER_INFO = run_redis_instance();
if ( !defined $SERVER_INFO ) {
  plan skip_all => 'redis-server is required for this test';
}
plan tests => 48;

my $REDIS;
my $T_IS_CONN = 0;
my $T_IS_DISCONN = 0;

ev_loop(
  sub {
    my $cv = shift;

    $REDIS = AnyEvent::Redis::RipeRedis->new(
      host               => $SERVER_INFO->{host},
      port               => $SERVER_INFO->{port},
      connection_timeout => 5,
      read_timeout       => 5,
      encoding           => 'utf8',

      on_connect => sub {
        $T_IS_CONN = 1;
        $cv->send();
      },
      on_disconnect => sub {
        $T_IS_DISCONN = 1;
      },
    );
  },
);

ok( $T_IS_CONN, 'on_connect' );

t_status_reply_mth1( $REDIS );
t_status_reply_mth2( $REDIS );

t_numeric_reply_mth1( $REDIS );
t_numeric_reply_mth2( $REDIS );

t_bulk_reply_mth1( $REDIS );
t_bulk_reply_mth2( $REDIS );

t_set_undef_mth1( $REDIS );
t_set_undef_mth2( $REDIS );

t_get_undef_mth1( $REDIS );
t_get_undef_mth2( $REDIS );

t_set_utf8_string_mth1( $REDIS );
t_set_utf8_string_mth2( $REDIS );

t_get_utf8_string_mth1( $REDIS );
t_get_utf8_string_mth2( $REDIS );

t_get_non_existent_mth1( $REDIS );
t_get_non_existent_mth2( $REDIS );

t_mbulk_reply_mth1( $REDIS );
t_mbulk_reply_mth2( $REDIS );

t_mbulk_reply_empty_list_mth1( $REDIS );
t_mbulk_reply_empty_list_mth2( $REDIS );

t_mbulk_reply_undef_mth1( $REDIS );
t_mbulk_reply_undef_mth2( $REDIS );

t_nested_mbulk_reply_mth1( $REDIS );
t_nested_mbulk_reply_mth2( $REDIS );

t_oprn_error_mth1( $REDIS );
t_oprn_error_mth2( $REDIS );

t_default_on_error( $REDIS );

t_error_after_exec_mth1( $REDIS );
t_error_after_exec_mth2( $REDIS );

t_quit( $REDIS );


####
sub t_status_reply_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'foo', "some\r\nstring",
        { on_done => sub {
            $t_data = shift;
          },
        }
      );

      $redis->del( 'foo',
        { on_done => sub {
            $cv->send();
          }
        }
      );
    }
  );

  is( $t_data, 'OK', 'SET; \'on_done\' used; status reply' );

  return;
}

####
sub t_status_reply_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'foo', "some\r\nstring",
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
        }
      );

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
      $redis->del( 'foo',
        sub {
          if ( defined $_[1] ) {
            my $err_msg = $_[1];

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is( $t_data, 'OK', 'SET; \'on_reply\' used; status reply' );

  return;
}

####
sub t_numeric_reply_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->incr( 'bar',
        { on_done => sub {
            $t_data = shift;
          },
        }
      );

      $redis->del( 'bar',
        { on_done => sub {
            $cv->send();
          }
        }
      );
    }
  );

  is( $t_data, 1, 'INCR; \'on_done\' used; numeric reply' );

  return;
}

####
sub t_numeric_reply_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->incr( 'bar',
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
        }
      );

      $redis->del( 'bar',
        sub {
          if ( defined $_[1] ) {
            my $err_msg = $_[1];

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is( $t_data, 1, 'INCR; \'on_reply\' used; numeric reply' );

  return;
}

####
sub t_bulk_reply_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'foo', "some\r\nstring" );

      $redis->get( 'foo',
        { on_done => sub {
            $t_data = shift;
          },
        }
      );

      $redis->del( 'foo',
        { on_done => sub {
            $cv->send();
          }
        }
      );
    }
  );

  is( $t_data, "some\r\nstring", 'GET; \'on_done\' used; bulk reply' );

  return;
}

####
sub t_bulk_reply_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'foo', "some\r\nstring" );

      $redis->get( 'foo',
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
        }
      );

      $redis->del( 'foo',
        sub {
          if ( defined $_[1] ) {
            my $err_msg = $_[1];

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is( $t_data, "some\r\nstring", 'GET; \'on_reply\' used; bulk reply' );

  return;
}

####
sub t_set_undef_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'empty', undef,
        { on_done => sub {
            $t_data = shift;

            $cv->send();
          },
        }
      );
    }
  );

  is( $t_data, 'OK', 'SET; \'on_done\' used; undef' );

  return;
}

####
sub t_set_undef_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'empty', undef,
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }

          $cv->send();
        },
      );
    }
  );

  is( $t_data, 'OK', 'SET; \'on_reply\' used; undef' );

  return;
}

####
sub t_get_undef_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->get( 'empty',
        { on_done => sub {
            $t_data = shift;

            $cv->send();
          },
        }
      );
    }
  );

  is( $t_data, '', 'GET; \'on_done\' used; undef' );

  return;
}

####
sub t_get_undef_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->get( 'empty',
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is( $t_data, '', 'GET; \'on_reply\' used; undef' );

  return;
}

####
sub t_set_utf8_string_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'ключ', 'Значение',
        { on_done => sub {
            $t_data = shift;
          },
        }
      );

      $redis->del( 'ключ',
        { on_done => sub {
            $cv->send();
          }
        }
      );
    }
  );

  is( $t_data, 'OK', 'SET; \'on_done\' used; UTF-8 string' );

  return;
}

####
sub t_set_utf8_string_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'ключ', 'Значение',
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
        },
      );

      $redis->del( 'ключ',
        sub {
          if ( defined $_[1] ) {
            my $err_msg = $_[1];

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is( $t_data, 'OK', 'SET; \'on_reply\' used; UTF-8 string' );

  return;
}

####
sub t_get_utf8_string_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'ключ', 'Значение' );

      $redis->get( 'ключ',
        { on_done => sub {
            $t_data = shift;
          },
        }
      );

      $redis->del( 'ключ',
        { on_done => sub {
            $cv->send();
          }
        }
      );
    }
  );

  is( $t_data, 'Значение', 'GET; \'on_done\' used; UTF-8 string' );

  return;
}

####
sub t_get_utf8_string_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->set( 'ключ', 'Значение' );

      $redis->get( 'ключ',
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
        }
      );

      $redis->del( 'ключ',
        sub {
          if ( defined $_[1] ) {
            my $err_msg = $_[1];

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is( $t_data, 'Значение', 'GET; \'on_reply\' used; UTF-8 string' );

  return;
}

####
sub t_get_non_existent_mth1 {
  my $redis = shift;

  my $t_data = 'not_undef';

  ev_loop(
    sub {
      my $cv = shift;

      $redis->get( 'non_existent',
        { on_done => sub {
            $t_data = shift;
            $cv->send();
          },
        }
      );
    }
  );

  is( $t_data, undef, 'GET; \'on_done\' used; non existent key' );

  return;
}

####
sub t_get_non_existent_mth2 {
  my $redis = shift;

  my $t_data = 'not_undef';
  my $t_err_msg;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->get( 'non_existent',
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            $t_err_msg = shift;

            diag( $t_err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  ok( !defined $t_data && !defined $t_err_msg,
      'GET; \'on_reply\' used; non existent key' );

  return;
}

####
sub t_mbulk_reply_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      for ( my $i = 1; $i <= 3; $i++ ) {
        $redis->rpush( 'list', "element_$i" );
      }

      $redis->lrange( 'list', 0, -1,
        { on_done => sub {
            $t_data = shift;
          },
        }
      );

      $redis->del( 'list',
        { on_done => sub {
            $cv->send();
          }
        }
      );
    }
  );

  is_deeply( $t_data,
    [ qw(
        element_1
        element_2
        element_3
      )
    ],
    'LRANGE; \'on_done\' used; multi-bulk reply'
  );

  return;
}

####
sub t_mbulk_reply_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      for ( my $i = 1; $i <= 3; $i++ ) {
        $redis->rpush( 'list', "element_$i" );
      }

      $redis->lrange( 'list', 0, -1,
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
        }
      );

      $redis->del( 'list',
        sub {
          if ( defined $_[1] ) {
            my $err_msg = $_[1];

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is_deeply( $t_data,
    [ qw(
        element_1
        element_2
        element_3
      )
    ],
    'LRANGE; \'on_reply\' used; multi-bulk reply'
  );

  return;
}

####
sub t_mbulk_reply_empty_list_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->lrange( 'non_existent', 0, -1,
        { on_done => sub {
            $t_data = shift;
            $cv->send();
          },
        }
      );
    },
  );

  is_deeply( $t_data, [], 'LRANGE; \'on_done\' used; empty list' );

  return;
}

####
sub t_mbulk_reply_empty_list_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->lrange( 'non_existent', 0, -1,
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    },
  );

  is_deeply( $t_data, [], 'LRANGE; \'on_reply\' used; empty list' );

  return;
}

####
sub t_mbulk_reply_undef_mth1 {
  my $redis = shift;

  my $t_data = 'not_undef';

  ev_loop(
    sub {
      my $cv = shift;

      $redis->brpop( 'non_existent', '1',
        { on_done => sub {
            $t_data = shift;
            $cv->send();
          },
        }
      );
    }
  );

  is( $t_data, undef, 'BRPOP; \'on_done\' used; multi-bulk undef' );

  return;
}

####
sub t_mbulk_reply_undef_mth2 {
  my $redis = shift;

  my $t_data = 'not_undef';
  my $t_err_msg;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->brpop( 'non_existent', '1',
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            $t_err_msg = shift;

            diag( $t_err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  ok( !defined $t_data && !defined $t_err_msg,
      'BRPOP; \'on_reply\' used; multi-bulk undef' );

  return;
}

####
sub t_nested_mbulk_reply_mth1 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      for ( my $i = 1; $i <= 3; $i++ ) {
        $redis->rpush( 'list', "element_$i" );
      }

      $redis->set( 'foo', "some\r\nstring" );

      $redis->multi();
      $redis->incr( 'bar' );
      $redis->lrange( 'list', 0, -1 );
      $redis->lrange( 'non_existent', 0, -1 );
      $redis->get( 'foo' );
      $redis->lrange( 'list', 0, -1 );
      $redis->exec(
        { on_done => sub {
            $t_data = shift;
          },
        }
      );

      $redis->del( qw( foo list bar ),
        { on_done => sub {
            $cv->send();
          },
        }
      );
    }
  );

  is_deeply( $t_data,
    [ 1,
      [ qw(
          element_1
          element_2
          element_3
        )
      ],
      [],
      "some\r\nstring",
      [ qw(
          element_1
          element_2
          element_3
        )
      ],
    ],
    'EXEC; \'on_done\' used; nested multi-bulk reply'
  );

  return;
}

####
sub t_nested_mbulk_reply_mth2 {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      for ( my $i = 1; $i <= 3; $i++ ) {
        $redis->rpush( 'list', "element_$i" );
      }

      $redis->set( 'foo', "some\r\nstring" );

      $redis->multi();
      $redis->incr( 'bar' );
      $redis->lrange( 'list', 0, -1 );
      $redis->lrange( 'non_existent', 0, -1 );
      $redis->get( 'foo' );
      $redis->lrange( 'list', 0, -1 );
      $redis->exec(
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            my $err_msg = shift;

            diag( $err_msg );
          }
        },
      );

      $redis->del( qw( foo list bar ),
        sub {
          if ( defined $_[1] ) {
            my $err_msg = $_[1];

            diag( $err_msg );
          }

          $cv->send();
        }
      );
    }
  );

  is_deeply( $t_data,
    [ 1,
      [ qw(
          element_1
          element_2
          element_3
        )
      ],
      [],
      "some\r\nstring",
      [ qw(
          element_1
          element_2
          element_3
        )
      ],
    ],
    'EXEC; \'on_reply\' used; nested multi-bulk reply'
  );

  return;
}

####
sub t_oprn_error_mth1 {
  my $redis = shift;

  my $t_err_msg;
  my $t_err_code;

  ev_loop(
    sub {
      my $cv = shift;

      # missing argument
      $redis->set( 'foo',
        { on_error => sub {
            $t_err_msg  = shift;
            $t_err_code = shift;

            $cv->send();
          },
        }
      );
    }
  );

  my $t_npref = 'operation error; \'on_error\' used';
  ok( defined $t_err_msg, "$t_npref; error message" );
  is( $t_err_code, E_OPRN_ERROR, "$t_npref; error code" );

  return;
}

####
sub t_oprn_error_mth2 {
  my $redis = shift;

  my $t_err_msg;
  my $t_err_code;

  ev_loop(
    sub {
      my $cv = shift;

      # missing argument
      $redis->set( 'foo',
        sub {
          my $data = shift;

          if ( defined $_[0] ) {
            $t_err_msg  = shift;
            $t_err_code = shift;
          }

          $cv->send();
        }
      );
    }
  );

  my $t_npref = 'operation error; \'on_reply\' used';
  ok( defined $t_err_msg, "$t_npref; error message" );
  is( $t_err_code, E_OPRN_ERROR, "$t_npref; error code" );

  return;
}

####
sub t_default_on_error {
  my $redis = shift;

  my $cv;
  my $t_err_msg;

  local $SIG{__WARN__} = sub {
    $t_err_msg = shift;

    chomp( $t_err_msg );
    $cv->send();
  };

  ev_loop(
    sub {
      $cv = shift;

      $redis->set(); # missing argument
    }
  );

  ok( defined $t_err_msg, "Default 'on_error' callback" );

  return;
}

####
sub t_error_after_exec_mth1 {
  my $redis = shift;

  my $t_err_msg;
  my $t_err_code;
  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->multi();
      $redis->set( 'foo', 'string' );
      $redis->incr( 'foo' );
      $redis->exec(
        { on_error => sub {
            $t_err_msg  = shift;
            $t_err_code = shift;
            $t_data     = shift;

            $cv->send();
          },
        }
      );
    }
  );

  my $t_npref = 'error after EXEC; \'on_error\' used';
  is( $t_err_msg, 'Operation \'exec\' completed with errors.',
      "$t_npref; error message" );
  is( $t_err_code, E_OPRN_ERROR, "$t_npref; error code" );
  is( $t_data->[0], 'OK', "$t_npref; status reply" );

  isa_ok( $t_data->[1], 'AnyEvent::Redis::RipeRedis::Error' );
  can_ok( $t_data->[1], 'code' );
  can_ok( $t_data->[1], 'message' );
  ok( defined $t_data->[1]->message(), "$t_npref; nested error message" );
  is( $t_data->[1]->code(), E_OPRN_ERROR, "$t_npref; nested error message" );

  return;
}

####
sub t_error_after_exec_mth2 {
  my $redis = shift;

  my $t_err_msg;
  my $t_err_code;
  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->multi();
      $redis->set( 'foo', 'string' );
      $redis->incr( 'foo' );
      $redis->exec(
        sub {
          $t_data = shift;

          if ( defined $_[0] ) {
            $t_err_msg  = shift;
            $t_err_code = shift;
          }

          $cv->send();
        },
      );
    }
  );

  my $t_npref = 'error after EXEC; \'on_reply\' used';
  is( $t_err_msg, 'Operation \'exec\' completed with errors.',
      "$t_npref; error message" );
  is( $t_err_code, E_OPRN_ERROR, "$t_npref; error code" );
  is( $t_data->[0], 'OK', "$t_npref; status reply" );

  isa_ok( $t_data->[1], 'AnyEvent::Redis::RipeRedis::Error' );
  can_ok( $t_data->[1], 'code' );
  can_ok( $t_data->[1], 'message' );
  ok( defined $t_data->[1]->message(), "$t_npref; nested error message" );
  is( $t_data->[1]->code(), E_OPRN_ERROR, "$t_npref; nested error message" );

  return;
}

####
sub t_quit {
  my $redis = shift;

  my $t_data;

  ev_loop(
    sub {
      my $cv = shift;

      $redis->quit(
        { on_done => sub {
            $t_data = shift;
            $cv->send();
          },
        }
      );
    }
  );

  is( $t_data, 'OK', 'QUIT; status reply; disconnect' );
  ok( $T_IS_DISCONN, 'on_disconnect' );

  return;
}
