use strict;
use warnings;
use Test::More;
use Test::Exception;
use Try::Tiny;
use File::Path 'rmtree';
use DBIx::Class::Optional::Dependencies;
use DBIx::Class::Schema::Loader 'make_schema_at';
use Scope::Guard ();

use lib qw(t/lib);

use dbixcsl_common_tests;
use dbixcsl_test_dir '$tdir';

use constant EXTRA_DUMP_DIR => "$tdir/sqlanywhere_extra_dump";

# The default max_cursor_count and max_statement_count settings of 50 are too
# low to run this test.
#
# Setting them to zero is preferred.

my %dsns;
for (qw(SQLANYWHERE SQLANYWHERE_ODBC)) {
    next unless $ENV{"DBICTEST_${_}_DSN"};

    my $dep_group = lc "rdbms_$_";
    if (!DBIx::Class::Optional::Dependencies->req_ok_for($dep_group)) {
        diag 'You need to install ' . DBIx::Class::Optional::Dependencies->req_missing_for($dep_group)
            . " to test with $_";
        next;
    }

    $dsns{$_}{dsn} = $ENV{"DBICTEST_${_}_DSN"};
    $dsns{$_}{user} = $ENV{"DBICTEST_${_}_USER"};
    $dsns{$_}{password} = $ENV{"DBICTEST_${_}_PASS"};
};

plan skip_all => 'You need to set the DBICTEST_SQLANYWHERE_DSN, _USER and _PASS and/or the DBICTEST_SQLANYWHERE_ODBC_DSN, _USER and _PASS environment variables'
    unless %dsns;

my ($schema, $schemas_created); # for cleanup in END for extra tests

my $tester = dbixcsl_common_tests->new(
    vendor      => 'SQLAnywhere',
    auto_inc_pk => 'INTEGER IDENTITY NOT NULL PRIMARY KEY',
    connect_info => [ map { $dsns{$_} } sort keys %dsns ],
    loader_options => { preserve_case => 1 },
    default_is_deferrable => 1,
    default_on_clause => 'RESTRICT',
    data_types  => {
        # http://infocenter.sybase.com/help/topic/com.sybase.help.sqlanywhere.11.0.1/dbreference_en11/rf-datatypes.html
        #
        # Numeric types
        'bit'         => { data_type => 'bit' },
        'tinyint'     => { data_type => 'tinyint' },
        'smallint'    => { data_type => 'smallint' },
        'int'         => { data_type => 'integer' },
        'integer'     => { data_type => 'integer' },
        'bigint'      => { data_type => 'bigint' },
        'float'       => { data_type => 'real' },
        'real'        => { data_type => 'real' },
        'double'      => { data_type => 'double precision' },
        'double precision' =>
                         { data_type => 'double precision' },

        'float(2)'    => { data_type => 'real' },
        'float(24)'   => { data_type => 'real' },
        'float(25)'   => { data_type => 'double precision' },
        'float(53)'   => { data_type => 'double precision' },

        # This test only works with the default precision and scale options.
        #
        # They are preserved even for the default values, because the defaults
        # can be changed.
        'decimal'     => { data_type => 'decimal', size => [30,6] },
        'dec'         => { data_type => 'decimal', size => [30,6] },
        'numeric'     => { data_type => 'numeric', size => [30,6] },

        'decimal(3)'   => { data_type => 'decimal', size => [3,0] },
        'dec(3)'       => { data_type => 'decimal', size => [3,0] },
        'numeric(3)'   => { data_type => 'numeric', size => [3,0] },

        'decimal(3,3)' => { data_type => 'decimal', size => [3,3] },
        'dec(3,3)'     => { data_type => 'decimal', size => [3,3] },
        'numeric(3,3)' => { data_type => 'numeric', size => [3,3] },

        'decimal(18,18)' => { data_type => 'decimal', size => [18,18] },
        'dec(18,18)'     => { data_type => 'decimal', size => [18,18] },
        'numeric(18,18)' => { data_type => 'numeric', size => [18,18] },

        # money types
        'money'        => { data_type => 'money' },
        'smallmoney'   => { data_type => 'smallmoney' },

        # bit arrays
        'long varbit'  => { data_type => 'long varbit' },
        'long bit varying'
                       => { data_type => 'long varbit' },
        'varbit'       => { data_type => 'varbit', size => 1 },
        'varbit(20)'   => { data_type => 'varbit', size => 20 },
        'bit varying'  => { data_type => 'varbit', size => 1 },
        'bit varying(20)'
                       => { data_type => 'varbit', size => 20 },

        # Date and Time Types
        'date'        => { data_type => 'date' },
        'datetime'    => { data_type => 'datetime' },
        'smalldatetime'
                      => { data_type => 'smalldatetime' },
        'timestamp'   => { data_type => 'timestamp' },
        # rewrite 'current timestamp' as 'current_timestamp'
        'timestamp default current timestamp'
                      => { data_type => 'timestamp', default_value => \'current_timestamp',
                           original => { default_value => \'current timestamp' } },
        'time'        => { data_type => 'time' },

        # String Types
        'char'         => { data_type => 'char',      size => 1  },
        'char(11)'     => { data_type => 'char',      size => 11 },
        'nchar'        => { data_type => 'nchar',     size => 1  },
        'nchar(11)'    => { data_type => 'nchar',     size => 11 },
        'varchar'      => { data_type => 'varchar',   size => 1  },
        'varchar(20)'  => { data_type => 'varchar',   size => 20 },
        'char varying(20)'
                       => { data_type => 'varchar',   size => 20 },
        'character varying(20)'
                       => { data_type => 'varchar',   size => 20 },
        'nvarchar(20)' => { data_type => 'nvarchar',  size => 20 },
        'xml'          => { data_type => 'xml' },
        'uniqueidentifierstr'
                       => { data_type => 'uniqueidentifierstr' },

        # Binary types
        'binary'       => { data_type => 'binary', size => 1 },
        'binary(20)'   => { data_type => 'binary', size => 20 },
        'varbinary'    => { data_type => 'varbinary', size => 1 },
        'varbinary(20)'=> { data_type => 'varbinary', size => 20 },
        'uniqueidentifier'
                       => { data_type => 'uniqueidentifier' },

        # Blob types
        'long binary'  => { data_type => 'long binary' },
        'image'        => { data_type => 'image' },
        'long varchar' => { data_type => 'long varchar' },
        'text'         => { data_type => 'text' },
        'long nvarchar'=> { data_type => 'long nvarchar' },
        'ntext'        => { data_type => 'ntext' },
    },
    extra => {
        create => [
            # 4 through 8 are used for the multi-schema tests
            q{
                create table sqlanywhere_loader_test9 (
                    id int identity not null primary key
                )
            },
            q{
                create table sqlanywhere_loader_test10 (
                    id int identity not null primary key,
                    nine_id int,
                    foreign key (nine_id) references sqlanywhere_loader_test9(id)
                        on delete cascade on update set null
                )
            },
        ],
        drop  => [ qw/sqlanywhere_loader_test9 sqlanywhere_loader_test10/ ],
        count => 4 + 30 * 2,
        run => sub {
            SKIP: {
                $schema  = $_[0];
                my $self = $_[3];

                # test on delete/update fk clause introspection
                ok ((my $rel_info = $schema->source('SqlanywhereLoaderTest10')->relationship_info('nine')),
                    'got rel info');

                is $rel_info->{attrs}{on_delete}, 'CASCADE',
                    'ON DELETE clause introspected correctly';

                is $rel_info->{attrs}{on_update}, 'SET NULL',
                    'ON UPDATE clause introspected correctly';

                is $rel_info->{attrs}{is_deferrable}, 1,
                    'is_deferrable defaults to 1';

                my $connect_info = [@$self{qw/dsn user password/}];

                my $dbh = $schema->storage->dbh;

                try {
                    $dbh->do("CREATE USER dbicsl_test1 identified by 'dbicsl'");
                }
                catch {
                    $schemas_created = 0;
                    skip "no CREATE USER privileges", 30 * 2;
                };

                $dbh->do(<<"EOF");
                    CREATE TABLE dbicsl_test1.sqlanywhere_loader_test4 (
                        id INT IDENTITY NOT NULL PRIMARY KEY,
                        value VARCHAR(100)
                    )
EOF
                $dbh->do(<<"EOF");
                    CREATE TABLE dbicsl_test1.sqlanywhere_loader_test5 (
                        id INT IDENTITY NOT NULL PRIMARY KEY,
                        value VARCHAR(100),
                        four_id INTEGER NOT NULL,
                        CONSTRAINT loader_test5_uniq UNIQUE (four_id),
                        FOREIGN KEY (four_id) REFERENCES dbicsl_test1.sqlanywhere_loader_test4 (id)
                    )
EOF
                $dbh->do("CREATE USER dbicsl_test2 identified by 'dbicsl'");
                $dbh->do(<<"EOF");
                    CREATE TABLE dbicsl_test2.sqlanywhere_loader_test5 (
                        pk INT IDENTITY NOT NULL PRIMARY KEY,
                        value VARCHAR(100),
                        four_id INTEGER NOT NULL,
                        CONSTRAINT loader_test5_uniq UNIQUE (four_id),
                        FOREIGN KEY (four_id) REFERENCES dbicsl_test1.sqlanywhere_loader_test4 (id)
                    )
EOF
                $dbh->do(<<"EOF");
                    CREATE TABLE dbicsl_test2.sqlanywhere_loader_test6 (
                        id INT IDENTITY NOT NULL PRIMARY KEY,
                        value VARCHAR(100),
                        sqlanywhere_loader_test4_id INTEGER,
                        FOREIGN KEY (sqlanywhere_loader_test4_id) REFERENCES dbicsl_test1.sqlanywhere_loader_test4 (id)
                    )
EOF
                $dbh->do(<<"EOF");
                    CREATE TABLE dbicsl_test2.sqlanywhere_loader_test7 (
                        id INT IDENTITY NOT NULL PRIMARY KEY,
                        value VARCHAR(100),
                        six_id INTEGER NOT NULL UNIQUE,
                        FOREIGN KEY (six_id) REFERENCES dbicsl_test2.sqlanywhere_loader_test6 (id)
                    )
EOF
                $dbh->do(<<"EOF");
                    CREATE TABLE dbicsl_test1.sqlanywhere_loader_test8 (
                        id INT IDENTITY NOT NULL PRIMARY KEY,
                        value VARCHAR(100),
                        sqlanywhere_loader_test7_id INTEGER,
                        FOREIGN KEY (sqlanywhere_loader_test7_id) REFERENCES dbicsl_test2.sqlanywhere_loader_test7 (id)
                    )
EOF

                $schemas_created = 1;

                my $guard = Scope::Guard->new(\&extra_cleanup);

                foreach my $db_schema (['dbicsl_test1', 'dbicsl_test2'], '%') {
                    lives_and {
                        rmtree EXTRA_DUMP_DIR;

                        my @warns;
                        local $SIG{__WARN__} = sub {
                            push @warns, $_[0] unless $_[0] =~ /\bcollides\b/;
                        };

                        make_schema_at(
                            'SQLAnywhereMultiSchema',
                            {
                                naming => 'current',
                                db_schema => $db_schema,
                                dump_directory => EXTRA_DUMP_DIR,
                                quiet => 1,
                            },
                            $connect_info,
                        );

                        diag join "\n", @warns if @warns;

                        is @warns, 0;
                    } 'dumped schema for dbicsl_test1 and dbicsl_test2 schemas with no warnings';

                    my ($test_schema, $rsrc, $rs, $row, %uniqs, $rel_info);

                    lives_and {
                        ok $test_schema = SQLAnywhereMultiSchema->connect(@$connect_info);
                    } 'connected test schema';

                    lives_and {
                        ok $rsrc = $test_schema->source('SqlanywhereLoaderTest4');
                    } 'got source for table in schema one';

                    is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
                        'column in schema one';

                    is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
                        'column in schema one';

                    is try { $rsrc->column_info('value')->{size} }, 100,
                        'column in schema one';

                    lives_and {
                        ok $rs = $test_schema->resultset('SqlanywhereLoaderTest4');
                    } 'got resultset for table in schema one';

                    lives_and {
                        ok $row = $rs->create({ value => 'foo' });
                    } 'executed SQL on table in schema one';

                    $rel_info = try { $rsrc->relationship_info('dbicsl_test1_sqlanywhere_loader_test5') };

                    is_deeply $rel_info->{cond}, {
                        'foreign.four_id' => 'self.id'
                    }, 'relationship in schema one';

                    is $rel_info->{attrs}{accessor}, 'single',
                        'relationship in schema one';

                    is $rel_info->{attrs}{join_type}, 'LEFT',
                        'relationship in schema one';

                    lives_and {
                        ok $rsrc = $test_schema->source('DbicslTest1SqlanywhereLoaderTest5');
                    } 'got source for table in schema one';

                    %uniqs = try { $rsrc->unique_constraints };

                    is keys %uniqs, 2,
                        'got unique and primary constraint in schema one';

                    delete $uniqs{primary};

                    is_deeply ((values %uniqs)[0], ['four_id'],
                        'correct unique constraint in schema one');

                    lives_and {
                        ok $rsrc = $test_schema->source('SqlanywhereLoaderTest6');
                    } 'got source for table in schema two';

                    is try { $rsrc->column_info('id')->{is_auto_increment} }, 1,
                        'column in schema two introspected correctly';

                    is try { $rsrc->column_info('value')->{data_type} }, 'varchar',
                        'column in schema two introspected correctly';

                    is try { $rsrc->column_info('value')->{size} }, 100,
                        'column in schema two introspected correctly';

                    lives_and {
                        ok $rs = $test_schema->resultset('SqlanywhereLoaderTest6');
                    } 'got resultset for table in schema two';

                    lives_and {
                        ok $row = $rs->create({ value => 'foo' });
                    } 'executed SQL on table in schema two';

                    $rel_info = try { $rsrc->relationship_info('sqlanywhere_loader_test7') };

                    is_deeply $rel_info->{cond}, {
                        'foreign.six_id' => 'self.id'
                    }, 'relationship in schema two';

                    is $rel_info->{attrs}{accessor}, 'single',
                        'relationship in schema two';

                    is $rel_info->{attrs}{join_type}, 'LEFT',
                        'relationship in schema two';

                    lives_and {
                        ok $rsrc = $test_schema->source('SqlanywhereLoaderTest7');
                    } 'got source for table in schema two';

                    %uniqs = try { $rsrc->unique_constraints };

                    is keys %uniqs, 2,
                        'got unique and primary constraint in schema two';

                    delete $uniqs{primary};

                    is_deeply ((values %uniqs)[0], ['six_id'],
                        'correct unique constraint in schema two');

                    lives_and {
                        ok $test_schema->source('SqlanywhereLoaderTest6')
                            ->has_relationship('sqlanywhere_loader_test4');
                    } 'cross-schema relationship in multi-db_schema';

                    lives_and {
                        ok $test_schema->source('SqlanywhereLoaderTest4')
                            ->has_relationship('sqlanywhere_loader_test6s');
                    } 'cross-schema relationship in multi-db_schema';

                    lives_and {
                        ok $test_schema->source('SqlanywhereLoaderTest8')
                            ->has_relationship('sqlanywhere_loader_test7');
                    } 'cross-schema relationship in multi-db_schema';

                    lives_and {
                        ok $test_schema->source('SqlanywhereLoaderTest7')
                            ->has_relationship('sqlanywhere_loader_test8s');
                    } 'cross-schema relationship in multi-db_schema';
                }
            }
        },
    },
);

$tester->run_tests();

sub extra_cleanup {
    if (not $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP}) {
        if ($schemas_created && (my $dbh = try { $schema->storage->dbh })) {
            foreach my $table ('dbicsl_test1.sqlanywhere_loader_test8',
                               'dbicsl_test2.sqlanywhere_loader_test7',
                               'dbicsl_test2.sqlanywhere_loader_test6',
                               'dbicsl_test2.sqlanywhere_loader_test5',
                               'dbicsl_test1.sqlanywhere_loader_test5',
                               'dbicsl_test1.sqlanywhere_loader_test4') {
                try {
                    $dbh->do("DROP TABLE $table");
                }
                catch {
                    diag "Error dropping table: $_";
                };
            }

            foreach my $db_schema (qw/dbicsl_test1 dbicsl_test2/) {
                try {
                    $dbh->do("DROP USER $db_schema");
                }
                catch {
                    diag "Error dropping test user $db_schema: $_";
                };
            }
        }
        rmtree EXTRA_DUMP_DIR;
    }
}
# vim:et sts=4 sw=4 tw=0:
