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

# Live PostgreSQL integration: exercises the Pg-specific code paths that the
# SQLite/MariaDB suites do not cover. Gated on:
#   DBIX_FAST_TEST_PG='postgres://postgres:test123@127.0.0.1:5433/dbix_test'

my $dsn = $ENV{DBIX_FAST_TEST_PG}
  or plan skip_all => 'DBIX_FAST_TEST_PG env var not set '
                    . '(e.g. postgres://postgres:pass@127.0.0.1:5433/dbix_test)';

eval { require DBD::Pg; 1 }    or plan skip_all => 'DBD::Pg required';
eval { require DBIx::Fast; 1 } or plan skip_all => 'DBIx::Fast required';
DBIx::Fast->import;

my $db = eval { DBIx::Fast->new(dsn => $dsn, RaiseError => 1, PrintError => 0) };
plan skip_all => "Cannot connect to PostgreSQL ($dsn): $@" if $@ or !$db;

# Suppress NOTICE chatter (e.g. "table ... does not exist, skipping" from
# DROP TABLE IF EXISTS) so the test output stays clean.
$db->exec(q{SET client_min_messages TO WARNING});

is($db->dbd, 'Pg', 'driver resolved to Pg');

$db->exec('DROP TABLE IF EXISTS pgint_items');
$db->exec(q{
    CREATE TABLE pgint_items (
        id      SERIAL PRIMARY KEY,
        name    TEXT NOT NULL,
        qty     INTEGER,
        note    TEXT,
        created TIMESTAMP
    )
});
$db->exec('CREATE INDEX idx_items_name ON pgint_items (name)');

subtest 'CRUD + last_id' => sub {
    my $id = $db->insert('pgint_items', { name => 'apple', qty => 10 });
    ok($id, 'insert returns last_id');
    $db->up('pgint_items', { qty => 11 }, { id => $id });
    is($db->val('SELECT qty FROM pgint_items WHERE id = ?', $id), 11, 'update applied');
    $db->insert('pgint_items', { name => 'bread', qty => 50 });
    $db->insert('pgint_items', { name => 'cheese', qty => 100 });
    $db->insert('pgint_items', { name => 'nullqty', qty => undef });
    $db->delete('pgint_items', { name => 'bread' });
    is($db->count('pgint_items'), 3, 'count after delete');
};

subtest 'count() + _make_where on Pg (BETWEEN, IS NULL, IS NOT NULL)' => sub {
    is($db->count('pgint_items', { qty => { BETWEEN => [10, 100] } }), 2,
       'BETWEEN counts the inclusive range');
    is($db->count('pgint_items', { qty => undef }), 1, 'qty IS NULL counts the null row');
    is($db->count('pgint_items', { qty => { '!=' => undef } }), 2, 'qty IS NOT NULL');
    like($db->sql, qr/IS NOT NULL/, 'emits IS NOT NULL on Pg');
};

subtest 'make_sen :: cast is preserved on Pg' => sub {
    # This is the case make_sen had to learn not to mangle - Pg actually parses ::
    my $row = $db->execute(
        'SELECT id::text AS sid, name FROM pgint_items WHERE name = :name',
        { name => 'apple' }, 'hash');
    is($row->{name}, 'apple', 'named param bound, :: cast intact');
    like($row->{sid}, qr/^\d+$/, 'id::text returned as text');
};

subtest 'transactions: commit, rollback, savepoints' => sub {
    $db->txn(sub { $db->insert('pgint_items', { name => 'txn1', qty => 1 }) });
    is($db->count('pgint_items', { name => 'txn1' }), 1, 'committed txn persisted');

    eval { $db->txn(sub {
        $db->insert('pgint_items', { name => 'txn2', qty => 2 });
        die "rollback\n";
    }) };
    is($db->count('pgint_items', { name => 'txn2' }), 0, 'failed txn rolled back');

    $db->txn(sub {
        $db->insert('pgint_items', { name => 'sp_base', qty => 1 });
        $db->transaction->savepoint('sp1');
        $db->insert('pgint_items', { name => 'sp_after', qty => 2 });
        $db->transaction->rollback_to('sp1');
    });
    is($db->count('pgint_items', { name => 'sp_base' }),  1, 'pre-savepoint insert kept');
    is($db->count('pgint_items', { name => 'sp_after' }), 0, 'post-savepoint insert rolled back');
};

subtest 'isolation level applied and restored on Pg' => sub {
    my $base = $db->val('SHOW default_transaction_isolation');
    $db->txn(sub {
        my $cur = $db->val('SHOW transaction_isolation');
        like($cur, qr/serializable/i, 'SERIALIZABLE applied inside txn');
    }, { isolation => 'SERIALIZABLE' });
    is($db->val('SHOW default_transaction_isolation'), $base,
       'session isolation restored after txn');
};

subtest 'schema introspection (Pg branches)' => sub {
    my $pk = $db->schema->primary_keys('pgint_items');
    is_deeply($pk, ['id'], 'primary_keys returns the serial PK');

    my $idx = $db->schema->get_indexes('pgint_items');
    ok(scalar(grep { $_->{column_name} eq 'name' } @$idx),
       'get_indexes finds the name index');

    my $info = $db->schema->table_info('pgint_items');
    ok(scalar(grep { $_->{column_name} eq 'qty' } @$info),
       'table_info lists the qty column');

    my $size = $db->schema->table_size('pgint_items');
    ok(defined $size->{rows},       'table_size returns rows');
    ok(defined $size->{size_bytes}, 'table_size returns size_bytes');
};

subtest 'insert_many + upsert_many on Pg' => sub {
    $db->exec('DROP TABLE IF EXISTS pgint_bulk');
    $db->exec('CREATE TABLE pgint_bulk (sku TEXT PRIMARY KEY, stock INTEGER)');
    my $n = $db->insert_many('pgint_bulk', [
        { sku => 'A', stock => 1 },
        { sku => 'B', stock => 2 },
    ]);
    is($n, 2, 'insert_many inserted 2 rows');
    $db->upsert_many('pgint_bulk',
        [ { sku => 'B', stock => 20 }, { sku => 'C', stock => 3 } ],
        ['sku'], ['stock']);
    is($db->val('SELECT stock FROM pgint_bulk WHERE sku = ?', 'B'), 20, 'upsert updated B');
    is($db->val('SELECT stock FROM pgint_bulk WHERE sku = ?', 'C'), 3,  'upsert inserted C');
};

done_testing;
