use strict;
use warnings;

use Test::More;

use lib 't/lib';

use Fey::ORM::Test qw( schema );
use Fey::Placeholder;
use List::Util qw( first );

my $Schema = schema();

{
    package Schema;

    use Fey::ORM::Schema;

    has_schema $Schema;
}

{
    package Message;

    use Fey::ORM::Table;

    has_table $Schema->table('Message');

    has_one $Schema->table('User');
}

{
    can_ok( 'Message', 'user' );

    my $attr = Message->meta()->get_attribute('user');
    ok( $attr, 'found attribute for user' );
    is(
        ref $attr->default(), 'CODE',
        'user attribute default is a coderef'
    );
    is(
        $attr->type_constraint()->name(), 'Fey::Object::Table',
        'user attribute type constraint is Fey::Object::Table'
    );

    my @ones = Message->meta()->has_ones();
    is(
        scalar @ones, 1,
        'one has_one for Message class'
    );

    my $ho = $ones[0];
    isa_ok( $ho, 'Fey::Meta::HasOne::ViaFK' );
    is(
        $ho->associated_class(), Message->meta(),
        'associated_class is Message->meta()'
    );
    is(
        $ho->name(), 'user',
        'name is user'
    );
    is(
        $ho->table(), $Schema->table('Message'),
        'table is Message table object'
    );
    is(
        $ho->foreign_table(), $Schema->table('User'),
        'foreign_table is User table object'
    );
    ok( $ho->is_cached(),     'is_cached is true' );
    ok( !$ho->allows_undef(), 'allows_undef is false' );
    is(
        $ho->fk()->source_table(), $Schema->table('Message'),
        'fk source table is Message'
    );
    is(
        $ho->fk()->target_table(), $Schema->table('User'),
        'fk target table is User'
    );
    is(
        $ho->associated_method(), undef,
        'associated_method is undef'
    );

    my $assoc_attr = $ho->associated_attribute();
    is(
        $assoc_attr, $attr,
        'associated attribute is same as the one in the metaclass'
    );
}

{
    package Message;

    use Fey::ORM::Table;

    __PACKAGE__->meta()->remove_has_one('user');

    ::is(
        scalar __PACKAGE__->meta()->has_ones(), 0,
        'no has_ones after calling remove_has_one'
    );
    ::ok(
        !__PACKAGE__->meta()->has_attribute('user'),
        'does not have a user attribute after calling remove_has_one'
    );

    has_one 'user' => (
        table => $Schema->table('User'),
        undef => 1,
    );
}

{
    my $attr = Message->meta()->get_attribute('user');
    is(
        $attr->type_constraint()->name(), 'Maybe[Fey::Object::Table]',
        'user attribute type constraint is Maybe[Fey::Object::Table]'
    );

    my @ones = Message->meta()->has_ones();
    is(
        scalar @ones, 1,
        'one has_one for Message class'
    );
    ok( $ones[0]->allows_undef, 'allows_undef is true' );
}

{
    package Message;

    __PACKAGE__->meta()->remove_has_one('user');

    has_one 'my_user' => (
        table => $Schema->table('User'),
        cache => 1,
    );
}

{
    can_ok( 'Message', 'my_user' );
    can_ok( 'Message', '_clear_my_user' );

    my $attr = Message->meta()->get_attribute('my_user');
    ok( $attr, 'found attribute for my_user' );
    is(
        ref $attr->default(), 'CODE',
        'my_user attribute default is a coderef'
    );
    is(
        $attr->type_constraint()->name(), 'Fey::Object::Table',
        'my_user attribute type constraint is Fey::Object::Table'
    );
}

{
    package Message;

    __PACKAGE__->meta()->remove_has_one('my_user');

    has_one 'user' => (
        table => $Schema->table('User'),
        cache => 0,
    );
}

{
    can_ok( 'Message', 'user' );
    ok( !Message->can('_clear_user'), 'no clearer for non-cached has_one' );

    ok(
        !Message->meta()->has_attribute('user'),
        'Message does not have an attribute for user (but does have a user() method)'
    );

    my @ones = Message->meta()->has_ones();
    is(
        scalar @ones, 1,
        'one has_one for Message class'
    );
    is(
        $ones[0]->associated_method(), Message->meta()->get_method('user'),
        'associated_method matches user method in Message class'
    );
}

{
    package Message;

    use Fey::ORM::Table;

    __PACKAGE__->meta()->remove_has_one('user');

    has_one 'user' => (
        table   => $Schema->table('User'),
        handles => [qw( username email )],
    );
}

{
    can_ok( 'Message', 'username' );
    can_ok( 'Message', 'email' );
}

{
    package Message;

    eval { has_one $Schema->table('Group') };

    ::like(
        $@,
        qr/\QThere are no foreign keys between the table for this class, Message and the table you passed to has_one(), Group/,
        'Cannot declare a has_one relationship to a table with which we have no FK'
    );

    eval { has_one $Schema->table('Message') };

    ::is(
        $@, '',
        'no exception declaring a self-referential has_one'
    );

    my $table = Fey::Table->new( name => 'NewTable' );
    eval { has_one $table };

    ::like(
        $@,
        qr/\QA table used for has-one or -many relationships must have a schema/,
        'table without a schema passed to has_one()'
    );
}

{
    my $editor_user_id = Fey::Column->new(
        name => 'editor_user_id',
        type => 'integer',
    );

    $Schema->table('Message')->add_column($editor_user_id);

    my $fk = Fey::FK->new(
        source_columns =>
            [ $Schema->table('Message')->column('editor_user_id') ],
        target_columns => [ $Schema->table('User')->column('user_id') ],
    );

    $Schema->add_foreign_key($fk);
}

{
    package Message;

    __PACKAGE__->meta()->remove_has_one('user');

    eval { has_one 'editor' => ( table => $Schema->table('User') ) };

    ::like(
        $@,
        qr/\QThere is more than one foreign key between the table for this class, Message and the table you passed to has_one(), User. You must specify one explicitly/i,
        'exception is thrown if trying to make a has_one() when there is >1 fk between the two tables'
    );

    my ($fk)
        = grep { $_->source_columns()->[0]->name() eq 'editor_user_id' }
        $Schema->foreign_keys_between_tables( 'Message', 'User' );

    eval {
        has_one 'editor' => (
            table => $Schema->table('User'),
            fk    => $fk,
        );
    };

    ::is( $@, '',
        'no error when specifying passing a disambiguating fk to has_one' );

    my @ones = Message->meta()->has_ones();
    ::is(
        $ones[0]->fk(), $fk,
        'fk matches the one passed to has_one'
    );
}

{
    package Message;

    __PACKAGE__->meta()->remove_has_one( $_->name() )
        for __PACKAGE__->meta()->has_ones();

    my $select
        = Fey::SQL->new_select()->select( $Schema->table('Message') )->where(
        $Schema->table('Message')->column('parent_message_id'),
        '=', Fey::Placeholder->new()
        )->order_by( $Schema->table('Message')->column('message_id'), 'DESC' )
        ->limit(1);

    has_one 'most_recent_child' => (
        table       => $Schema->table('Message'),
        select      => $select,
        bind_params => sub { $_[0]->message_id() },
    );
}

{
    can_ok( 'Message', 'most_recent_child' );

    my $attr = Message->meta()->get_attribute('most_recent_child');
    ok( $attr, 'found attribute for most_recent_child' );
    is(
        ref $attr->default(), 'CODE',
        'most_recent_child attribute default is a coderef'
    );
    is(
        $attr->type_constraint()->name(), 'Maybe[Fey::Object::Table]',
        'most_recent_child attribute type constraint is Maybe[Fey::Object::Table]'
    );

    my @ones = Message->meta()->has_ones();
    is(
        scalar @ones, 1,
        'one has_one for Message class'
    );

    my $ho = $ones[0];
    isa_ok( $ho, 'Fey::Meta::HasOne::ViaSelect' );
    is(
        $ho->name(), 'most_recent_child',
        'name is most_recent_child'
    );
    ok( $ho->allows_undef(), 'allows_undef is true' );
}

{
    package Message;

    __PACKAGE__->meta()->remove_has_one('most_recent_child');

    my $select
        = Fey::SQL->new_select()->select( $Schema->table('Message') )
        ->from( $Schema->table('Message') )->where(
        $Schema->table('Message')->column('parent_message_id'),
        '=', Fey::Placeholder->new()
        )->order_by( $Schema->table('Message')->column('message_id'), 'DESC' )
        ->limit(1);

    has_one 'most_recent_child' => (
        table       => $Schema->table('Message'),
        select      => $select,
        bind_params => sub { $_[0]->message_id() },
        cache       => 0,
    );
}

{
    can_ok( 'Message', 'most_recent_child' );

    ok(
        !Message->meta()->get_attribute('most_recent_child'),
        'Message does not have a most_recent_child attribute, but does have a method for it'
    );
}

done_testing();
