use strict;
use warnings;
use Test::More;
use Test::Exception;
use DBIx::Fast;

# Regression tests for the deadlock-retry state-unwind bug (rank 1, CRITICAL)
# and the nested-transaction stats corruption (rank 25).
#
# The deadlock is simulated by dying with a message containing "deadlock"
# from the code block; retry detection must keep matching that text in
# addition to driver error codes.

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,
    PrintError => 0,
);

$db->exec(q{ CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT) });

# --- rank 1: a deadlock retry must roll back the failed attempt, re-begin a
#     clean outermost transaction, commit the successful retry, and leave
#     level/autocommit pristine. ---
subtest 'deadlock retry rolls back, re-begins, and commits the retry' => sub {
    my $attempts = 0;
    my $result = $db->txn(sub {
        $attempts++;
        $db->exec('INSERT INTO t (id, name) VALUES (?, ?)', $attempts, "row$attempts");
        die "deadlock detected\n" if $attempts == 1;
        return 'ok';
    }, { retry_delay => 0 });

    is($attempts, 2,   'code block retried exactly once');
    is($result,   'ok','txn returned the successful result');
    is($db->transaction->level, 0, 'level reset to 0 after retry (object not poisoned)');
    ok($db->db->dbh->{AutoCommit}, 'connection back in autocommit (txn properly closed)');

    is($db->val('SELECT COUNT(*) FROM t WHERE name = ?', 'row1'), 0,
       'first (deadlocked) attempt was rolled back');
    is($db->val('SELECT COUNT(*) FROM t WHERE name = ?', 'row2'), 1,
       'successful retry was committed');
};

# --- the Transaction object must still work for the NEXT transaction ---
subtest 'subsequent independent transaction still commits' => sub {
    lives_ok {
        $db->txn(sub {
            $db->exec('INSERT INTO t (id, name) VALUES (?, ?)', 100, 'after');
        });
    } 'second txn ran without error';
    is($db->transaction->level, 0, 'level still 0');
    is($db->val('SELECT COUNT(*) FROM t WHERE name = ?', 'after'), 1,
       'second txn committed durably');
};

# --- retries exhausted: final rollback, exception propagates, clean state ---
subtest 'exhausted retries: rollback + propagate + clean level' => sub {
    $db->exec('DELETE FROM t');
    my $attempts = 0;
    throws_ok {
        $db->txn(sub {
            $attempts++;
            $db->exec('INSERT INTO t (id, name) VALUES (?, ?)', 200 + $attempts, "x$attempts");
            die "deadlock detected\n";
        }, { max_retries => 2, retry_delay => 0 });
    } qr/deadlock/, 'deadlock propagates after exhausting retries';

    is($attempts, 3, 'tried initial attempt + 2 retries');
    is($db->transaction->level, 0, 'level reset to 0 after exhaustion');
    ok($db->db->dbh->{AutoCommit}, 'autocommit restored after exhaustion');
    is($db->val('SELECT COUNT(*) FROM t'), 0, 'no partial writes survived the rollback');
    ok($db->transaction->get_stats->{deadlock_count} > 0, 'deadlock was counted');
};

# --- rank 25: nested do() must count as ONE logical transaction ---
subtest 'nested transaction counts as a single transaction in stats' => sub {
    $db->transaction->reset_stats;
    $db->txn(sub {
        $db->exec('INSERT INTO t (id, name) VALUES (?, ?)', 300, 'outer');
        $db->txn(sub {
            $db->exec('INSERT INTO t (id, name) VALUES (?, ?)', 301, 'inner');
        });
    });
    my $st = $db->transaction->get_stats;
    is($st->{total_count},   1, 'nested do() counted as a single transaction');
    is($st->{success_count}, 1, 'one success recorded, not two');
    is($db->transaction->level, 0, 'level clean after nesting');
};

done_testing;
