use strict;
use warnings;

use Test::More;

use Fey::DBIManager::Source;

my $DSN      = 'dbi:Mock:foo';
my $Username = 'user';
my $Password = 'password';

my $can_modify_pid = eval { local $$ = $$ + 1; 1 };

{
    my $source = Fey::DBIManager::Source->new( dsn => $DSN );

    is( $source->name(), 'default', 'source is named default' );
    is( $source->dsn(), $DSN, 'dsn passed to new() is returned by dsn()' );
    is( $source->username(), '', 'default username is empty string' );
    is( $source->password(), '', 'default password is empty string' );
    is_deeply(
        $source->attributes(), {
            AutoCommit         => 1,
            RaiseError         => 1,
            PrintError         => 0,
            PrintWarn          => 1,
            ShowErrorStatement => 1,
        },
        'check default attributes'
    );
    ok( !$source->post_connect(), 'no post_connect hook by default' );
    ok( $source->auto_refresh(),  'auto_refresh defaults to true' );
    ok( !$source->_threaded(),    'threads is false' );

    my $sub = sub { };
    $source = Fey::DBIManager::Source->new(
        dsn        => $DSN,
        username   => $Username,
        password   => $Password,
        attributes => {
            AutoCommit => 0,
            SomeThing  => 1,
        },
        post_connect => $sub,
        auto_refresh => 0,
    );

    is( $source->dsn(), $DSN, 'dsn passed to new() is returned by dsn()' );
    is( $source->username(), $Username, 'username is user' );
    is( $source->password(), $Password, 'password is password' );
    is_deeply(
        $source->attributes(), {
            AutoCommit         => 1,
            RaiseError         => 1,
            PrintError         => 0,
            PrintWarn          => 1,
            ShowErrorStatement => 1,
            SomeThing          => 1,
        },
        'attributes include values passed in, except AutoCommit is 1'
    );
    is( $source->post_connect(), $sub, 'post_connect is set' );
    ok( !$source->auto_refresh(), 'auto_refresh  is false' );
}

eval <<'EOF';
{
    package threads;

    $threads::tid = 42;
    sub tid { $threads::tid }
}
EOF

{
    my $source = Fey::DBIManager::Source->new( dsn => $DSN );

    ok( $source->_threaded(), 'threads is true' );
}

{
    my $post_connect = 0;
    my $count        = 0;
    my $sub          = sub { $post_connect = shift; $count++ };

    my $source = Fey::DBIManager::Source->new(
        dsn        => $DSN,
        username   => $Username,
        password   => $Password,
        attributes => {
            AutoCommit => 0,
        },
        post_connect  => $sub,
        ping_interval => 0,
    );

    my $dbh = $source->dbh();

    test_dbh( $dbh, $post_connect );

    is( $count, 1, 'one DBI handle made so far' );

 SKIP:
    {
        skip 'Cannot modify $$ on this platform', 2,
            unless $can_modify_pid;

        local $$ = $$ + 1;
        $source->_ensure_fresh_dbh();

        ok( $dbh->{InactiveDestroy}, 'InactiveDestroy was set to true' );

        $dbh = $source->dbh();
        test_dbh( $dbh, $post_connect );

        is( $count, 2, 'new handle made when pid changes' );
    }

    # Need to do this since pid just changed again.
    $source->_unset_dbh(); $source->dbh();

    $threads::tid++;
    $source->_ensure_fresh_dbh();

    ok( !$dbh->{InactiveDestroy}, 'InactiveDestroy was not set' );

    $dbh = $source->dbh();
    test_dbh( $dbh, $post_connect );

    is( $count, 4, 'new handle made when tid changes' );

    $source->_ensure_fresh_dbh();
    is( $count, 4, 'no new handle made with same pid & tid' );

    $dbh->{mock_can_connect} = 0;
    $source->_ensure_fresh_dbh();

    is( $count, 5, 'new handle made when Active is false' );

    $dbh = $source->dbh();

    $dbh->{mock_can_connect} = 1;
    no warnings 'redefine';
    local *DBD::Mock::db::ping = sub { return 0 };

    $source->_ensure_fresh_dbh();
    is( $count, 6, 'new handle made when ping returns false' );
}

{
    my $count = 0;
    my $sub = sub { $count++ };

    my $source = Fey::DBIManager::Source->new(
        dsn        => $DSN,
        username   => $Username,
        password   => $Password,
        attributes => {
            AutoCommit => 0,
        },
        auto_refresh => 0,
        post_connect => $sub,
    );

    my $dbh = $source->dbh();

    is( $count, 1, 'one DBI handle made so far' );

 SKIP:
    {
        skip 'Cannot modify $$ on this platform', 1,
            unless $can_modify_pid;

        local $$ = $$ + 1;
        $dbh = $source->dbh();

        is( $count, 1, 'no new handle when pid changes' );
    }

    $threads::tid++;
    $dbh = $source->dbh();

    is( $count, 1, 'no new handle when tid changes' );

    $dbh->{mock_can_connect} = 0;
    $dbh = $source->dbh();

    is( $count, 1, 'no new handle made when Active is false' );

    $dbh->{mock_can_connect} = 1;
    no warnings 'redefine';
    local *DBD::Mock::db::ping = sub { return 0 };
    $dbh = $source->dbh();

    is( $count, 1, 'no new handle made when ping returns false' );
}

{
    my $source
        = Fey::DBIManager::Source->new( dsn => $DSN, name => 'another' );

    is(
        $source->name(), 'another',
        'explicit name passed to constructor'
    );
}

{
    my $source = Fey::DBIManager::Source->new( dsn => $DSN );

    ok(
        !$source->allows_nested_transactions(),
        'source allows nested transactions is false by default with DBD::Mock'
    );

    ok(
        $source->dbh()->{AutoCommit},
        'AutoCommit is true after checking allows_nested_transactions'
    );
}

{
    my $source = Fey::DBIManager::Source->new( dsn => $DSN );

    no warnings 'redefine', 'once';
    local *DBD::Mock::db::begin_work = sub { };
    local *DBD::Mock::db::rollback   = sub { };

    ok(
        $source->allows_nested_transactions(),
        'source allows nested transactions is true'
    );
}

{
    my %attr = Fey::DBIManager::Source->_required_dbh_attributes();
    my %bad_attr;

    $bad_attr{$_} = $attr{$_} ? 0 : 1 for keys %attr;

    my $dbh = DBI->connect( $DSN, '', '', \%bad_attr );
    my $source = Fey::DBIManager::Source->new( dsn => $DSN, dbh => $dbh );

    for my $k ( sort keys %attr ) {
        my $actual_val = $source->dbh()->{$k};
        ok(
            ( $attr{$k} ? $actual_val : !$actual_val ),
            "DBI attribute $k is set to required value for Source"
        );
    }
}

{
    my $connect = 0;

    my %p = (
        name         => 'foo',
        dsn          => $DSN,
        username     => 'foo',
        password     => 'bar',
        attributes   => { foo => 42 },
        post_connect => sub { $connect++ },
    );

    my $source = Fey::DBIManager::Source->new(%p);

    $source->dbh(); # force the creation of a handle

    ok( $source->_has_dbh(), 'source does have a handle' );

    my $clone = $source->clone();

    for my $attr (qw( dsn username password attributes )) {
        is_deeply(
            $source->$attr(), $clone->$attr(),
            "original and clone share the same $attr"
        );
    }

    ok( ! $clone->_has_dbh(), 'clone does not have a handle yet' );

    is(
        $clone->name(), 'Clone of foo',
        'name is Clone of foo'
    );

    isnt(
        $source->dbh(), $clone->dbh(),
        'original and clone have different handles'
    );

    is(
        $connect, 2,
        'post_connect sub has been called twice'
    );

    my $clone2 = $source->clone( name => 'bar' );

    is(
        $clone2->name(), 'bar',
        'can pass args to clone()'
    );
}

SKIP:
{
    skip 'These tests require Test::Output', 2
        unless eval "use Test::Output; 1";

    stderr_is(
        sub {
            ok(
                !Fey::DBIManager::Source->new( dsn => $DSN )
                    ->allows_nested_transactions(),
                'DBD::Mock does not support nested transactions'
            );
        },
        '',
        'no warnings checking for nested transaction support with DBD::Mock'
    );
}

sub test_dbh {
    my $dbh          = shift;
    my $post_connect = shift;

    local $Test::Builder::Level = $Test::Builder::Level + 1;

    isa_ok( $dbh, 'DBI::db' );
    is(
        $dbh->{Name}, 'foo',
        'db name passed to DBI->connect() is same as the one passed to new()'
    );
    is(
        $dbh->{Username}, $Username,
        'username passed to DBI->connect() is same as the one passed to new()'
    );
    is(
        $post_connect, $dbh,
        'post_connect sub was called with DBI handle as argument'
    );

    check_attributes($dbh);
}

sub check_attributes {
    my $dbh = shift;

    local $Test::Builder::Level = $Test::Builder::Level + 1;

    my %expect = (
        AutoCommit         => 1,
        RaiseError         => 1,
        PrintError         => 0,
        PrintWarn          => 1,
        ShowErrorStatement => 1,
    );
    for my $k ( sort keys %expect ) {
        ok(
            ( $expect{$k} ? $dbh->{$k} : !$dbh->{$k} ),
            "$k should be " . ( $expect{$k} ? 'true' : 'false' )
        );
    }
}

done_testing();
