#!perl
use strict;
use warnings;
use Test::More;
use Test::Exception;
use DBIx::Fast;

eval "use DBD::SQLite 1.74";
plan skip_all => "DBD::SQLite 1.74 required" if $@;

my $db = DBIx::Fast->new( dsn => 'dbi:SQLite:dbname=:memory:', driver => 'SQLite', RaiseError => 1 );

# Setup schema
$db->exec(q{ CREATE TABLE test_missing ( id INTEGER PRIMARY KEY, name TEXT, value INTEGER ) });

subtest 'q() method' => sub {
    $db->q('SELECT * FROM test_missing WHERE id = ?', 1);
    is($db->sql, 'SELECT * FROM test_missing WHERE id = ?', 'q() sets sql');
    is_deeply($db->p, [1], 'q() sets params');
};

subtest 'execute_prepare()' => sub {
    $db->sql('INSERT INTO test_missing (name, value) VALUES (?, ?)');
    lives_ok { $db->execute_prepare('test1', 100) } 'execute_prepare runs';
    is($db->last_sql, 'INSERT INTO test_missing (name, value) VALUES (?, ?)', 'last_sql set');

    my $count = $db->val('SELECT COUNT(*) FROM test_missing');
    is($count, 1, 'Row inserted via execute_prepare');
};

subtest 'up()' => sub {
    lives_ok {
        $db->up('test_missing', { value => 200 }, { name => 'test1' })
    } 'up runs';

    my $val = $db->val('SELECT value FROM test_missing WHERE name = ?', 'test1');
    is($val, 200, 'Value updated via up');
};

subtest 'make_sen()' => sub {
    $db->sql('SELECT * FROM test_missing WHERE name = :name AND value = :val');
    my $args = { name => 'test1', val => 200 };
    
    $db->make_sen($args);
    
    is($db->sql, 'SELECT * FROM test_missing WHERE name = ? AND value = ?', 'SQL placeholders replaced');
    is_deeply($db->p, ['test1', 200], 'Params extracted correctly');
    
    $db->sql('SELECT :x + :x as double_x');
    $db->make_sen({ x => 5 });
    is($db->sql, 'SELECT ? + ? as double_x', 'Multiple occurrences replaced');
    is_deeply($db->p, [5, 5], 'Params duplicated correctly');
};

subtest 'load_extension()' => sub {
    my $schema = $db->load_extension('Schema');
    isa_ok($schema, 'DBIx::Fast::Schema', 'Extension loaded');
    
    my $schema2 = $db->load_extension('Schema');
    is($schema, $schema2, 'Extension cached');
    
    throws_ok { $db->load_extension('NonExistent') } qr/Error: load_extension/, 'Loading non-existent extension fails';
};

# NOTE: _dsn_to_dbi URI mapping is covered in t/DSN.t (single source).

# --- 0.17 release-gate regressions -----------------------------------------

subtest 'execute() without $extra clears a stale $p' => sub {
    $db->exec('CREATE TABLE mc_ex (id INTEGER PRIMARY KEY)');
    $db->all('SELECT ? AS x', 5);                 # leaves $p = [5]
    my $r = $db->execute('SELECT 7 AS y');        # no binds - must not reuse [5]
    is($r->[0]{y}, 7, 'execute() with no params ignores the previous bind list');
};

subtest 'read methods do not kill a live query() iterator' => sub {
    $db->exec('CREATE TABLE mc_it (id INTEGER PRIMARY KEY)');
    $db->insert('mc_it', { id => $_ }) for 1 .. 3;

    my $rs = $db->query('SELECT id FROM mc_it ORDER BY id');
    my $first = $rs->hash;
    is($first->{id}, 1, 'iterator first row');

    $db->all('SELECT id FROM mc_it ORDER BY id');  # same SQL, 3-arg prepare_cached
    my $second = $rs->hash;
    is($second->{id}, 2, 'iterator survives a concurrent read with identical SQL');
};

subtest 'qualified table names work in count()' => sub {
    $db->exec('CREATE TABLE mc_q (id INTEGER PRIMARY KEY)');
    $db->insert('mc_q', { id => $_ }) for 1 .. 4;
    is($db->count('main.mc_q'), 4, 'count() quotes a schema.table name per segment');
};

subtest 'cache => 0 does not enable caching' => sub {
    my $db0 = DBIx::Fast->new( dsn => 'dbi:SQLite:dbname=:memory:',
                               driver => 'SQLite', cache => 0 );
    is($db0->cache, undef, 'a false cache option leaves caching disabled');
};

subtest 'savepoints do not leak across committed transactions' => sub {
    $db->exec('CREATE TABLE mc_sp (id INTEGER PRIMARY KEY)');
    $db->txn(sub {
        $db->insert('mc_sp', { id => 1 });
        $db->transaction->savepoint('spX');
    });
    # spX belonged to the committed txn; a new txn must not find it.
    my $r = $db->transaction->do(sub {
        my $ok = $db->transaction->rollback_to('spX');
        ok(!$ok, 'rollback_to a savepoint from a prior committed txn returns falsy');
        1;
    });
    is($db->transaction->level, 0, 'new transaction closed cleanly');
};

done_testing();
