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

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

subtest '_set_dbd and _set_dsn' => sub {
    my $orig_dbd = $db->dbd;
    my $orig_dsn = $db->dsn;

    $db->_set_dbd('TestDriver');
    is($db->dbd, 'TestDriver', '_set_dbd works');

    $db->_set_dsn('dbi:Test:dbname=fake');
    is($db->dsn, 'dbi:Test:dbname=fake', '_set_dsn works');

    # Restore
    $db->_set_dbd($orig_dbd);
    $db->_set_dsn($orig_dsn);
    is($db->dbd, $orig_dbd, 'dbd restored');
};

subtest 'errors reader - default empty array' => sub {
    my $db2 = DBIx::Fast->new(
        dsn        => 'dbi:SQLite:dbname=:memory:',
        driver     => 'SQLite',
        RaiseError => 1,
    );
    my $errors = $db2->errors;
    is(ref $errors, 'ARRAY', 'errors is arrayref');
    is(scalar @$errors, 0, 'errors starts empty');
};

subtest 'set_error and errors/last_error' => sub {
    $db->set_error(42, 'Test error message');
    my $errors = $db->errors;
    ok(scalar @$errors >= 1, 'errors has entries');

    my $last = $errors->[-1];
    is($last->{id}, 42, 'Error id correct');
    is($last->{error}, 'Test error message', 'Error message correct');
    ok($last->{time}, 'Error has timestamp');

    like($db->last_error, qr/42.*Test error message/, 'last_error formatted');
};

subtest 'tracker wiring in _track' => sub {
    # The per-method tracked matrix lives in t/profiler.t; here only the
    # wiring that file does not assert: last_sql via the tracker path and
    # the recorded query record shape.
    my $tracker = $db->tracker;
    $tracker->clear;

    $db->all('SELECT * FROM test_cover');
    is($tracker->get_stats->{total_queries}, 1, 'query tracked');
    ok($db->last_sql, 'last_sql set via tracker path');

    my $q = $tracker->queries->[-1];
    ok($q->{sql},            'query sql recorded');
    ok(defined $q->{params}, 'query params recorded');
    ok($q->{duration} >= 0,  'query duration recorded');
};

subtest 'execute() with hash type' => sub {
    $db->insert('test_cover', { name => 'hash_test', value => 42 });

    $db->execute(
        'SELECT * FROM test_cover WHERE name = :name',
        { name => 'hash_test' },
        'hash'
    );

    my $result = $db->results;
    is(ref $result, 'HASH', 'execute with hash type returns hashref');
    is($result->{name}, 'hash_test', 'Correct name');
    is($result->{value}, 42, 'Correct value');
};

subtest 'execute() with arrayref type (default)' => sub {
    $db->execute(
        'SELECT * FROM test_cover WHERE name = :name',
        { name => 'hash_test' },
        'arrayref'
    );

    my $result = $db->results;
    is(ref $result, 'ARRAY', 'execute with arrayref returns arrayref');
    ok(scalar @$result >= 1, 'Has results');
};

subtest 'execute() without extra params' => sub {
    # Reset p to avoid stale bind params
    $db->p(undef);
    $db->execute('SELECT COUNT(*) as cnt FROM test_cover');
    my $result = $db->results;
    is(ref $result, 'ARRAY', 'execute without extra returns arrayref');
    ok($result->[0]{cnt} >= 0, 'Count result');
};

# (Exception-always-croaks regression is pinned in t/bug_count_where.t)

subtest 'profiler() with SQLite driver' => sub {
    my $profiler = $db->profiler;
    ok($profiler, 'Profiler loaded');
    isa_ok($profiler, 'DBIx::Fast::Profile::SQLite');

    # Second call returns cached instance
    my $profiler2 = $db->profiler;
    is($profiler, $profiler2, 'Profiler cached');
};

subtest 'count with WHERE using operator hash' => sub {
    $db->exec('DELETE FROM test_cover');
    $db->insert('test_cover', { name => 'a', value => 10 });
    $db->insert('test_cover', { name => 'b', value => 20 });
    $db->insert('test_cover', { name => 'c', value => 30 });

    my $gt = $db->count('test_cover', { value => { '>' => 15 } });
    is($gt, 2, 'count with > operator');

    my $lt = $db->count('test_cover', { value => { '<' => 25 } });
    is($lt, 2, 'count with < operator');

    my $eq = $db->count('test_cover', { value => 20 });
    is($eq, 1, 'count with = (default)');
};

subtest '_make_where invalid operator' => sub {
    throws_ok {
        $db->count('test_cover', { value => { 'INVALID_OP' => 5 } })
    } qr/Invalid operator/, '_make_where rejects invalid operators';
};

subtest 'up() with time column' => sub {
    $db->insert('test_cover', { name => 'time_test', value => 1 });

    $db->up('test_cover', { value => 2 }, { name => 'time_test' }, 'created_at');

    my $row = $db->hash('SELECT * FROM test_cover WHERE name = ?', 'time_test');
    is($row->{value}, 2, 'Value updated');
    like($row->{created_at}, qr/^\d{4}-\d{2}-\d{2}/, 'Time column set');
};

subtest 'up() without time column' => sub {
    $db->up('test_cover', { value => 3 }, { name => 'time_test' });
    my $row = $db->hash('SELECT * FROM test_cover WHERE name = ?', 'time_test');
    is($row->{value}, 3, 'Value updated without time');
};

subtest '_safe_id with undef' => sub {
    throws_ok { $db->_safe_id(undef) }
        qr/Invalid identifier/, '_safe_id rejects undef';
};

subtest 'exec() DBI error path with silent mode' => sub {
    my $db_silent = DBIx::Fast->new(
        dsn        => 'dbi:SQLite:dbname=:memory:',
        driver     => 'SQLite',
        RaiseError => 0,
        PrintError => 0,
    );

    # Create table with UNIQUE constraint, then violate it
    $db_silent->exec(q{ CREATE TABLE err_test (id INTEGER PRIMARY KEY, name TEXT UNIQUE) });
    $db_silent->exec(q{ INSERT INTO err_test (id, name) VALUES (1, 'dup') });

    # This should fail silently (UNIQUE constraint violation)
    my $result = $db_silent->exec(q{ INSERT INTO err_test (id, name) VALUES (2, 'dup') });
    ok(!defined $result, 'exec returns undef on error in silent mode');

    my $errors = $db_silent->errors;
    ok(scalar @$errors >= 1, 'Error recorded in errors array');
};

subtest 'execute() hash type without named params (p=undef)' => sub {
    $db->exec(q{ CREATE TABLE IF NOT EXISTS test_cover2 (id INTEGER PRIMARY KEY, v TEXT) });
    $db->exec(q{ INSERT INTO test_cover2 (v) VALUES ('x') });

    $db->p(undef);
    $db->execute('SELECT * FROM test_cover2', undef, 'hash');
    my $result = $db->results;
    is(ref $result, 'HASH', 'hash type without params returns hashref');
};

subtest 'Exception with last_error context' => sub {
    $db->set_error(999, 'prior error');

    # Exception should include last_error
    throws_ok { $db->Exception("new problem") }
        qr/new problem.*Last error.*prior error/,
        'Exception includes last_error context';
};

subtest 'profiler() fallback to Base for unknown driver' => sub {
    my $db2 = DBIx::Fast->new(
        dsn        => 'dbi:SQLite:dbname=:memory:',
        driver     => 'SQLite',
        RaiseError => 1,
    );

    # Force unknown driver to trigger fallback
    $db2->_set_dbd('UnknownDriver');
    my $warned = '';
    local $SIG{__WARN__} = sub { $warned = $_[0] };
    my $profiler = $db2->profiler;
    isa_ok($profiler, 'DBIx::Fast::Profile::Base');
    like($warned, qr/No profile support/, 'Warning about unknown driver');
};

subtest 'array() returns empty arrayref for no results' => sub {
    $db->exec('DELETE FROM test_cover');
    my $result = $db->array('SELECT name FROM test_cover WHERE 1=0');
    is(ref $result, 'ARRAY', 'Returns arrayref');
    is(scalar @$result, 0, 'Empty array for no results');
};

done_testing;
