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

# Regression tests for:
#  - rank 3: a handle lost mid-transaction must NOT be silently reconnected as
#    a fresh autocommit session (that would break atomicity); it must die.
#  - rank 9: per-connection settings (HandleError) must be reinstalled on every
#    (re)connect, not only on the first handle.

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

# A fake DBI handle whose Active/AutoCommit we control, to drive Connector::dbh
# down its stale-handle path deterministically.
{
    package FakeHandle;
    sub new      { my ($c, %a) = @_; bless { %a }, $c }
    sub FETCH    { my ($s, $k) = @_; $s->{$k} }
    sub STORE    { my ($s, $k, $v) = @_; $s->{$k} = $v }
    sub ping     { 0 }
    sub disconnect { }
}

subtest 'rank 3: dead handle mid-transaction dies instead of reconnecting' => sub {
    my $conn = DBIx::Fast::Connector->new(
        dsn => 'dbi:SQLite:dbname=:memory:', attrs => { RaiseError => 1 });
    # Inject a dead handle that was inside a transaction (AutoCommit off).
    $conn->_test_set_handle(FakeHandle->new(Active => 0, AutoCommit => 0))
         ->_test_set_pid($$)      # avoid the fork path
         ->_test_skip_ping(1);    # use the client-side Active check

    throws_ok { $conn->dbh }
        qr/lost mid-transaction/i,
        'reconnect refused while a transaction was open';
};

subtest 'rank 3: dead handle in autocommit reconnects normally' => sub {
    my $conn = DBIx::Fast::Connector->new(
        dsn => 'dbi:SQLite:dbname=:memory:', attrs => { RaiseError => 1 });
    $conn->_test_set_handle(FakeHandle->new(Active => 0, AutoCommit => 1))
         ->_test_set_pid($$)
         ->_test_skip_ping(1);

    my $h;
    lives_ok { $h = $conn->dbh } 'reconnects when not in a transaction';
    ok($h, 'got a live handle back');
};

subtest 'R2.1: connector recovers after a mid-transaction loss' => sub {
    my $conn = DBIx::Fast::Connector->new(
        dsn => 'dbi:SQLite:dbname=:memory:', attrs => { RaiseError => 1 });
    $conn->_test_set_handle(FakeHandle->new(Active => 0, AutoCommit => 0))
         ->_test_set_pid($$)
         ->_test_skip_ping(1);

    throws_ok { $conn->dbh }
        qr/lost mid-transaction/i, 'first call dies (in-txn loss)';

    # The dead handle must be discarded, so the NEXT call reconnects cleanly
    # instead of dying forever (the connector must not be poisoned).
    my $h;
    lives_ok { $h = $conn->dbh } 'second call reconnects (connector not poisoned)';
    ok($h, 'got a live handle back');
};

subtest 'R2.1 (ping path): dead handle detected by ping, in-txn, recovers' => sub {
    local $ENV{DBIX_FAST_PING_INTERVAL} = 0;   # force a ping on every dbh()
    my $conn = DBIx::Fast::Connector->new(
        dsn => 'dbi:SQLite:dbname=:memory:', attrs => { RaiseError => 1 });
    # Active=1 so the client-side check passes and we reach the ping branch;
    # ping() returns 0 (dead), AutoCommit=0 means we were mid-transaction.
    $conn->_test_set_handle(FakeHandle->new(Active => 1, AutoCommit => 0))
         ->_test_set_pid($$)
         ->_test_skip_ping(0);

    throws_ok { $conn->dbh }
        qr/lost mid-transaction/i, 'ping-detected mid-txn loss dies';
    my $h;
    lives_ok { $h = $conn->dbh } 'reconnects cleanly after ping-path loss (not poisoned)';
    ok($h, 'got a live handle back');
};

subtest 'rank 9: HandleError is reinstalled after a reconnect' => sub {
    my $db = DBIx::Fast->new(
        dsn        => 'dbi:SQLite:dbname=:memory:',
        driver     => 'SQLite',
        RaiseError => 0,
        PrintError => 0,
    );
    $db->exec('CREATE TABLE t (id INTEGER)');

    # Force a reconnect (fresh handle from the Connector).
    $db->db->disconnect;

    my $before = scalar @{ $db->errors };
    eval { $db->all('SELECT * FROM does_not_exist') };   # records via HandleError
    ok(scalar @{ $db->errors } > $before,
       'DB error still captured into the errors buffer after reconnect');
};

done_testing;
