use strict;
use warnings;
use Test::More;
use Test::Exception;
use JSON::PP ();
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 t (id INTEGER, name TEXT) });
$db->exec(q{ INSERT INTO t VALUES (1, 'one') });

sub capture_stdout (&) {
    my ($code) = @_;
    my $out = '';
    do { local *STDOUT; open STDOUT, '>', \$out or die $!; $code->() };
    return $out;
}

# --- Mode validation -------------------------------------------------------

subtest 'output mode validation' => sub {
    require DBIx::Fast::Profiler;

    throws_ok { DBIx::Fast::Profiler->new(dbix => $db, output => 'xml') }
        qr/Invalid output mode/, 'invalid mode at construction croaks';

    my $p = DBIx::Fast::Profiler->new(dbix => $db, auto_print => 0);
    is $p->output, 'text', 'default mode is text';

    throws_ok { $p->output('xml') } qr/Invalid output mode/,
        'invalid mode via setter croaks';
    is $p->output, 'text', 'failed set leaves mode untouched';

    $p->output('json');
    is $p->output, 'json', 'json accepted';
    my $cb = sub { };
    $p->output($cb);
    is $p->output, $cb, 'coderef accepted';
    $p->output('text');
    is $p->output, 'text', 'setter round-trips back to text';
};

# --- JSON events from the tracker ------------------------------------------

subtest 'json query event' => sub {
    my $tr = $db->tracker;
    $tr->clear;
    $tr->output('json');
    $tr->auto_print(1);

    my $out = capture_stdout {
        $db->all('SELECT * FROM t WHERE id = ?', 42);
    };
    $tr->auto_print(0);

    my @lines = split /\n/, $out;
    is scalar @lines, 1, 'exactly one JSON line per query';

    my $ev = JSON::PP::decode_json($lines[0]);
    is $ev->{event},  'query',   'event type';
    is $ev->{source}, 'tracker', 'source is tracker';
    like $ev->{ts}, qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
        'ISO 8601 UTC timestamp';
    is $ev->{data}{sql}, 'SELECT * FROM t WHERE id = ?', 'raw SQL in data';
    is_deeply $ev->{data}{params}, [42], 'bind params in data';
    is $ev->{data}{severity}, 'ok', 'computed severity present';
    unlike $out, qr/\e\[/,      'no ANSI escapes in json mode';
    unlike $out, qr/Query at/,  'no text rendering in json mode';
};

subtest 'json stats and summary events match the get_* structs' => sub {
    my $tr = $db->tracker;
    $tr->output('json');

    my $out = capture_stdout { $tr->print_stats; $tr->print_summary };
    my ($stats_line, $summary_line) = split /\n/, $out;

    my $stats = JSON::PP::decode_json($stats_line);
    is $stats->{event}, 'stats', 'stats event';
    is $stats->{data}{total_queries}, $tr->get_stats->{total_queries},
        'stats data mirrors get_stats';

    my $summary = JSON::PP::decode_json($summary_line);
    is $summary->{event}, 'summary', 'summary event';
    is_deeply [ sort keys %{ $summary->{data} } ],
              [ sort keys %{ $tr->get_detailed_stats } ],
        'summary data mirrors get_detailed_stats';
};

# --- Callback mode ---------------------------------------------------------

subtest 'callback receives (event, data, meta); STDOUT stays silent' => sub {
    my @events;
    my $tr = $db->tracker;
    $tr->output(sub { push @events, [@_] });

    my $out = capture_stdout { $tr->print_stats };

    is $out, '', 'nothing printed in callback mode';
    is scalar @events, 1, 'callback fired once';
    my ($event, $data, $meta) = @{ $events[0] };
    is $event, 'stats', 'event name';
    is ref $data, 'HASH', 'data is the structured hashref';
    is $meta->{source}, 'tracker', 'meta carries source';
    ok $meta->{ts}, 'meta carries timestamp';
};

subtest 'a dying callback warns but never kills the query path' => sub {
    my @warnings;
    local $SIG{__WARN__} = sub { push @warnings, @_ };

    my $tr = $db->tracker;
    $tr->output(sub { die "sink exploded\n" });
    $tr->auto_print(1);

    lives_ok {
        capture_stdout { $db->all('SELECT * FROM t') };
    } 'query survives a dying output callback';
    $tr->auto_print(0);
    $tr->output('text');

    ok grep(/profiler output failed.*sink exploded/s, @warnings),
        'failure surfaced as a warning';
};

# --- Explicit filehandle ---------------------------------------------------

subtest 'output_fh redirects json lines away from STDOUT' => sub {
    my $sink = '';
    open my $fh, '>', \$sink or die $!;

    my $tr = $db->tracker;
    $tr->output('json');
    $tr->output_fh($fh);

    my $out = capture_stdout { $tr->print_stats };

    is $out, '', 'STDOUT untouched';
    like $sink, qr/"event":"stats"/, 'event landed in the filehandle';
    $tr->output_fh(undef);
    $tr->output('text');
};

# --- Text mode stays byte-compatible ---------------------------------------

subtest 'text mode renders as always' => sub {
    my $tr = $db->tracker;
    $tr->clear;
    $tr->auto_print(1);

    my $out = capture_stdout {
        $db->all('SELECT * FROM t WHERE id = ?', 42);
    };
    $tr->auto_print(0);

    like $out, qr/Query at/,    'text header present';
    like $out, qr/Duration/,    'duration line present';
    like $out, qr/Params.*42/,  'params rendered';
};

# --- Constructor plumbing --------------------------------------------------

subtest 'profile_output reaches tracker and profiler' => sub {
    my @events;
    my $db2 = DBIx::Fast->new(
        dsn            => 'dbi:SQLite:dbname=:memory:',
        driver         => 'SQLite',
        RaiseError     => 1, PrintError => 0,
        profile_output => 'json',
    );
    is $db2->tracker->output,  'json', 'tracker inherits profile_output';
    is $db2->profiler->output, 'json', 'profiler inherits profile_output';

    my $db3 = DBIx::Fast->new(
        dsn            => 'dbi:SQLite:dbname=:memory:',
        driver         => 'SQLite',
        RaiseError     => 1, PrintError => 0,
        profile_output => sub { push @events, $_[0] },
    );
    my $out = capture_stdout { $db3->tracker->print_stats };
    is $out, '', 'coderef via constructor: silent STDOUT';
    is_deeply \@events, ['stats'], 'coderef via constructor received the event';
};

# --- SQLite profile no-ops survive every mode ------------------------------

subtest 'SQLite profile no-ops in json mode' => sub {
    my $db4 = DBIx::Fast->new(
        dsn            => 'dbi:SQLite:dbname=:memory:',
        driver         => 'SQLite',
        RaiseError     => 1, PrintError => 0,
        profile_output => 'json',
    );
    lives_ok { $db4->profiler->print_stats } 'print_stats no-op lives';
    lives_ok { $db4->profiler->print_index_analysis('t') } 'index analysis no-op lives';
    lives_ok { $db4->profiler->print_connection_monitor } 'monitor no-op lives';
};

done_testing;
