use strict;
use warnings;

use Test::More;

use lib 't/lib';

use Fey::ORM::Test;
use Fey::Literal::String;
use Fey::Test;


Fey::ORM::Test::insert_user_data();
Fey::ORM::Test::define_live_classes();

plan tests => 67;


basic_tests();
add_transform();
tests_with_transform();

Fey::ORM::Test::insert_user_data();

User->meta()->make_immutable();
basic_tests();
tests_with_transform();


sub basic_tests
{

    {
        is( User->Count(), 2, 'Count() finds two rows' );
    }

    {
        my $user1 = User->new( user_id => 1 );
        ok( $user1, 'was able to load user where user_id = 1' );

        is( $user1->username(), 'autarch',
            'username is set as side effect of calling _get_column_values()' );

        if ( User->meta()->is_immutable() )
        {
            is( $user1->email()->as_string(), 'autarch@example.com',
                'email is set as side effect of calling _get_column_values()' );
        }
        else
        {
            is( $user1->email(), 'autarch@example.com',
                'email is set as side effect of calling _get_column_values()' );
        }

        my $user2 = User->new( user_id => 1 );
        isnt( $user1, $user2,
              'calling User->new() twice with the same user_id returns two different objects' );

        is( $user2->username(), 'autarch',
            'username is fetched as needed' );
        ok( $user2->has_email(),
            'email is set as side effect of calling username()' );
    }

    {
        my $user = User->new( user_id => 12458686 );

        is( $user, undef,
            'nonexistent user_id to new() returns undef' );
    }

    {
        my $new_called = 0;

        {
            no warnings 'redefine', 'once';
            local *User::new = sub { $new_called = 1 };

            User->insert( username => 'new',
                          email    => 'new@example.com' );
        }

        ok( ! $new_called, 'new() is not called when insert() is done in void context' );

        is( User->Count(), 3, 'Count() is now 3' );

        my $user = User->insert( username => 'new2',
                                 email    => 'new@example.com' );

        is( $user->username(), 'new2',
            'object returned from insert() has username = new2' );
        cmp_ok( $user->user_id(), '>', 0,
                'object returned from insert() has a user id > 0 (fetched via last_insert_id())' );
        is( User->Count(), 4, 'Count() is now 4' );

        my $string = Fey::Literal::String->new( 'literal' );

        $user = User->insert( username => $string,
                              email    => 'new@example.com' );

        is( $user->username(), 'literal',
            'literals are handled correctly in an insert' );
    }

    {
        my $user = User->new( username => 'autarch' );
        is( $user->user_id(), 1,
            'got expected user when creating object via username' );
    }

    {
        User->new( username => 'does not exist at all' );
        like( $@, qr/Could not find a row in User where username =/i,
              'error message when we cannot find a matching row in the dbms' );

        User->new();
        like( $@, qr/Could not find a row in User matching the values you provided/i,
              'error message when we cannot find a matching row for any keys' );
    }

    {
        my @users = User->insert_many( { username => 'new3',
                                         email    => 'new3@example.com',
                                       },
                                       { username => 'new4',
                                         email    => 'new4@example.com',
                                       },
                                     );

        is( @users, 2, 'two new users were inserted' );
        is_deeply( [ map { $_->username() } @users ],
                   [ qw( new3 new4 ) ],
                   'users were returned with expected data in the order they were provided'
                 );
    }

    {
        my $user = User->new( user_id => 1 );
        $user->update( username => 'updated',
                       email    => 'updated@example.com' );

        ok( $user->has_email(), 'email is not cleared when update value is a non-reference' );
        is( $user->username(), 'updated', 'username = updated' );

        if ( User->meta()->is_immutable() )
        {
            is( $user->email()->as_string(), 'updated@example.com', 'email = updated@example.com' );
        }
        else
        {
            is( $user->email(), 'updated@example.com', 'email = updated@example.com' );
        }

        my $string = Fey::Literal::String->new( 'updated2' );
        $user->update( username => $string );

        ok( ! $user->has_username(), 'username is cleared when update value is a reference' );
        is( $user->username(), 'updated2', 'username = updated2' );
    }

    {
        my $load_from_dbms_called = 0;
        my $user;

        {
            no warnings 'redefine', 'once';
            local *User::_load_from_dbms = sub { $load_from_dbms_called = 1 };

            $user = User->new( user_id     => 99,
                               username    => 'not in dbms',
                               email       => 'notindbms@example.com',
                               _from_query => 1,
                             );
        }

        ok( ! $load_from_dbms_called,
            '_load_from_dbms() is not called when _from_query is passed to the constructor' );
        is( $user->username(), 'not in dbms',
            'data passed to constructor is available from object' );
    }

    {
        eval
        {
            User->new( email       => 'notindbms@example.com',
                       _from_query => 1,
                     );
        };

        like( $@, qr/pass the primary key/,
              'new() with _from_query requires the primary key' );

        my $user = User->new( user_id     => 99,
                              _from_query => 1,
                            );

        is( $user->user_id(), 99,
            'new() with _from_query works when given a candidate key' );
    }
}

sub tests_with_transform
{
    {
        my $user = User->new( user_id => 1 );

        isa_ok( $user->email(), 'Email' );
    }

    {
        my $user = User->new( user_id => 1 );

        my $email = Email->new( 'another@example.com' );

        $user->update( email => $email );

        is ( $user->email()->as_string(), $email->as_string(),
             'deflator intercepts Email object passed to update and turns it into a string' );

        my $dbh = $user->_dbh();
        my $sql = q{SELECT email FROM "User" WHERE user_id = ?};
        my $email_in_dbms = ( $dbh->selectcol_arrayref( $sql, {}, $user->user_id() ) )->[0];

        is( $email_in_dbms, $email->as_string(),
            'check email in dbms after update with deflator' );
    }

    {
        my $email = Email->new( 'inserting@example.com' );

        my $user =
            User->insert( username => 'inserting',
                          email    => $email,
                        );

        is( $user->email()->as_string(), $email->as_string(),
            'deflator intercepts Email object passed to insert and turns it into a string' );

        my $dbh = $user->_dbh();
        my $sql = q{SELECT email FROM "User" WHERE user_id = ?};
        my $email_in_dbms = ( $dbh->selectcol_arrayref( $sql, {}, $user->user_id() ) )->[0];

        is( $email_in_dbms, $email->as_string(),
            'check email in dbms after insert with deflator' );
    }
}

{
    my $user = User->new( user_id => 1 );
    $user->delete();

    ok( ! User->new( user_id => 1 ),
        'after delete() user is no longer in dbms' );
}

sub add_transform
{
    package Email;

    sub new
    {
        my $string = $_[1];

        return bless \$string, $_[0];
    }

    sub as_string
    {
        return ${ $_[0] };
    }

    package User;

    use Scalar::Util qw( blessed );

    use Fey::ORM::Table;

    transform 'email'
        => inflate { return Email->new( $_[1] ) }
        => deflate { return blessed $_[1] ? $_[1]->as_string() : $_[1] };
}
