#!/usr/bin/perl

# Unit tests for Net::Nostr::Group

use strictures 2;

use Test2::V0 -no_srand => 1;

use lib 't/lib';
use TestFixtures qw(make_event);

use Net::Nostr::Bech32 qw(encode_naddr);
use Net::Nostr::Group;

my $pk = 'a' x 64;
my $target_pk = 'b' x 64;
my $eid = '1' x 64;
my $relay = 'wss://groups.nostr.com';

###############################################################################
# parse_id
###############################################################################

subtest 'parse_id naddr basic' => sub {
    my $naddr = encode_naddr(
        identifier => 'grp',
        pubkey     => $pk,
        kind       => 39000,
        relays     => [$relay],
    );
    my $r = Net::Nostr::Group->parse_id($naddr);
    is($r->{group_id}, 'grp', 'group_id');
    is($r->{pubkey}, $pk, 'relay self pubkey');
    is($r->{kind}, 39000, 'metadata kind');
    is($r->{relays}, [$relay], 'relay hint');
    is($r->{relay}, $relay, 'first relay hint shortcut');
};

subtest 'parse_id rejects legacy host-only form' => sub {
    ok(dies { Net::Nostr::Group->parse_id("host.com") }, 'legacy host-only id rejected');
};

subtest 'parse_id rejects legacy host-tick-group form' => sub {
    ok(dies { Net::Nostr::Group->parse_id("h'BAD") }, 'legacy host tick id rejected');
};

###############################################################################
# format_id
###############################################################################

subtest 'format_id' => sub {
    my $id = Net::Nostr::Group->format_id(
        pubkey   => $pk,
        group_id => 'g',
        relay    => $relay,
    );
    my $parsed = Net::Nostr::Group->parse_id($id);
    is($parsed->{group_id}, 'g', 'group_id');
    is($parsed->{pubkey}, $pk, 'pubkey');
    is($parsed->{relays}, [$relay], 'relay');
};

subtest 'format_id croaks without pubkey' => sub {
    ok(dies { Net::Nostr::Group->format_id(group_id => 'g') }, 'croaks');
};

subtest 'format_id croaks without group_id' => sub {
    ok(dies { Net::Nostr::Group->format_id(host => 'h') }, 'croaks');
};

###############################################################################
# validate_group_id
###############################################################################

subtest 'validate_group_id' => sub {
    ok(Net::Nostr::Group->validate_group_id('ok-id_1'), 'valid');
    ok(Net::Nostr::Group->validate_group_id('NO'), 'uppercase valid');
    ok(Net::Nostr::Group->validate_group_id('has spaces'), 'spaces valid');
    ok(!Net::Nostr::Group->validate_group_id(undef), 'undef');
    ok(!Net::Nostr::Group->validate_group_id(''), 'empty invalid');
};

###############################################################################
# Event builders: kind, h tag, content
###############################################################################

subtest 'put_user kind and tags' => sub {
    my $e = Net::Nostr::Group->put_user(
        pubkey => $pk, group_id => 'g', target => $target_pk,
        roles => ['admin'],
    );
    is($e->kind, 9000, 'kind');
    my @h = grep { $_->[0] eq 'h' } @{$e->tags};
    is($h[0][1], 'g', 'h tag');
    my @p = grep { $_->[0] eq 'p' } @{$e->tags};
    is($p[0][1], $target_pk, 'target');
    is($p[0][2], 'admin', 'role');
};

subtest 'remove_user kind' => sub {
    my $e = Net::Nostr::Group->remove_user(
        pubkey => $pk, group_id => 'g', target => $target_pk,
    );
    is($e->kind, 9001, 'kind');
};

subtest 'edit_metadata kind and metadata tags' => sub {
    my $e = Net::Nostr::Group->edit_metadata(
        pubkey => $pk, group_id => 'g', name => 'N', about => 'A',
    );
    is($e->kind, 9002, 'kind');
    my @name = grep { $_->[0] eq 'name' } @{$e->tags};
    is($name[0][1], 'N', 'name tag');
};

subtest 'delete_event kind and e tag' => sub {
    my $e = Net::Nostr::Group->delete_event(
        pubkey => $pk, group_id => 'g', event_id => $eid,
    );
    is($e->kind, 9005, 'kind');
    my @etag = grep { $_->[0] eq 'e' } @{$e->tags};
    is($etag[0][1], $eid, 'event_id');
};

subtest 'create_group kind' => sub {
    my $e = Net::Nostr::Group->create_group(pubkey => $pk, group_id => 'g');
    is($e->kind, 9007, 'kind');
};

subtest 'delete_group kind' => sub {
    my $e = Net::Nostr::Group->delete_group(pubkey => $pk, group_id => 'g');
    is($e->kind, 9008, 'kind');
};

subtest 'create_invite kind and code' => sub {
    my $e = Net::Nostr::Group->create_invite(
        pubkey => $pk, group_id => 'g', code => 'xyz',
    );
    is($e->kind, 9009, 'kind');
    my @c = grep { $_->[0] eq 'code' } @{$e->tags};
    is($c[0][1], 'xyz', 'code');
};

subtest 'join_request kind' => sub {
    my $e = Net::Nostr::Group->join_request(pubkey => $pk, group_id => 'g');
    is($e->kind, 9021, 'kind');
};

subtest 'leave_request kind' => sub {
    my $e = Net::Nostr::Group->leave_request(pubkey => $pk, group_id => 'g');
    is($e->kind, 9022, 'kind');
};

###############################################################################
# Metadata events (addressable, d tag)
###############################################################################

subtest 'metadata kind and d tag' => sub {
    my $e = Net::Nostr::Group->metadata(
        pubkey => $pk, group_id => 'g', name => 'N',
    );
    is($e->kind, 39000, 'kind');
    is($e->d_tag, 'g', 'd_tag');
};

subtest 'metadata livekit and supported_kinds tags' => sub {
    my $e = Net::Nostr::Group->metadata(
        pubkey          => $pk,
        group_id        => 'g',
        livekit         => 1,
        supported_kinds => [9, 11],
    );
    my @livekit = grep { $_->[0] eq 'livekit' } @{$e->tags};
    my @supported = grep { $_->[0] eq 'supported_kinds' } @{$e->tags};
    is($livekit[0], ['livekit'], 'livekit tag');
    is($supported[0], ['supported_kinds', '9', '11'], 'supported_kinds tag');
};

subtest 'participants kind and parser' => sub {
    my $e = Net::Nostr::Group->participants(
        pubkey       => $pk,
        group_id     => 'g',
        participants => [$target_pk],
    );
    is($e->kind, 39004, 'kind');
    my $parsed = Net::Nostr::Group->participants_from_event($e);
    is($parsed->{group_id}, 'g', 'group_id');
    is($parsed->{participants}, [$target_pk], 'participants');
};

subtest 'admins kind' => sub {
    my $e = Net::Nostr::Group->admins(
        pubkey => $pk, group_id => 'g',
        members => [{ pubkey => $target_pk, roles => ['admin'] }],
    );
    is($e->kind, 39001, 'kind');
};

subtest 'members kind' => sub {
    my $e = Net::Nostr::Group->members(
        pubkey => $pk, group_id => 'g', members => [$target_pk],
    );
    is($e->kind, 39002, 'kind');
};

subtest 'roles kind' => sub {
    my $e = Net::Nostr::Group->roles(
        pubkey => $pk, group_id => 'g',
        roles => [{ name => 'admin' }],
    );
    is($e->kind, 39003, 'kind');
};

###############################################################################
# Parsing
###############################################################################

subtest 'metadata_from_event' => sub {
    my $e = make_event(
        pubkey => $pk, kind => 39000, content => '',
        tags => [['d', 'g'], ['name', 'N'], ['private'], ['livekit'], ['supported_kinds', '9']],
    );
    my $m = Net::Nostr::Group->metadata_from_event($e);
    is($m->{group_id}, 'g', 'group_id');
    is($m->{name}, 'N', 'name');
    is($m->{private}, 1, 'private');
    is($m->{livekit}, 1, 'livekit');
    is($m->{supported_kinds}, ['9'], 'supported kinds');
};

subtest 'admins_from_event' => sub {
    my $e = make_event(
        pubkey => $pk, kind => 39001, content => '',
        tags => [['d', 'g'], ['p', $target_pk, 'admin']],
    );
    my $r = Net::Nostr::Group->admins_from_event($e);
    is($r->{admins}[0]{pubkey}, $target_pk, 'pubkey');
    is($r->{admins}[0]{roles}, ['admin'], 'roles');
};

subtest 'members_from_event' => sub {
    my $e = make_event(
        pubkey => $pk, kind => 39002, content => '',
        tags => [['d', 'g'], ['p', $target_pk]],
    );
    my $r = Net::Nostr::Group->members_from_event($e);
    is($r->{members}, [$target_pk], 'members');
};

subtest 'roles_from_event' => sub {
    my $e = make_event(
        pubkey => $pk, kind => 39003, content => '',
        tags => [['d', 'g'], ['role', 'admin', 'desc']],
    );
    my $r = Net::Nostr::Group->roles_from_event($e);
    is($r->{roles}[0]{name}, 'admin', 'name');
    is($r->{roles}[0]{description}, 'desc', 'description');
};

subtest 'group_id_from_event h vs d' => sub {
    my $h = make_event(
        pubkey => $pk, kind => 9021, content => '',
        tags => [['h', 'from-h']],
    );
    my $d = make_event(
        pubkey => $pk, kind => 39000, content => '',
        tags => [['d', 'from-d']],
    );
    is(Net::Nostr::Group->group_id_from_event($h), 'from-h', 'h tag');
    is(Net::Nostr::Group->group_id_from_event($d), 'from-d', 'd tag');
};

subtest 'group_id_from_event returns undef' => sub {
    my $e = make_event(pubkey => $pk, kind => 1, content => '', tags => []);
    is(Net::Nostr::Group->group_id_from_event($e), undef, 'undef');
};

###############################################################################
# Validation errors
###############################################################################

subtest 'croaks on missing required params' => sub {
    ok(dies { Net::Nostr::Group->put_user(pubkey => $pk, group_id => 'g') },
        'put_user without target');
    ok(dies { Net::Nostr::Group->delete_event(pubkey => $pk, group_id => 'g') },
        'delete_event without event_id');
    ok(dies { Net::Nostr::Group->create_invite(pubkey => $pk, group_id => 'g') },
        'create_invite without code');
    ok(dies { Net::Nostr::Group->metadata_from_event(
        make_event(pubkey => $pk, kind => 1, content => '')) },
        'metadata_from_event wrong kind');
};

subtest 'group_id accepts arbitrary non-empty strings' => sub {
    ok(lives { Net::Nostr::Group->join_request(pubkey => $pk, group_id => 'BAD') },
        'uppercase accepted');
    ok(lives { Net::Nostr::Group->create_group(pubkey => $pk, group_id => 'a b') },
        'space accepted');
    ok(dies { Net::Nostr::Group->create_group(pubkey => $pk, group_id => '') },
        'empty rejected');
};

###############################################################################
# previous tag
###############################################################################

subtest 'previous tag in builders' => sub {
    my $e = Net::Nostr::Group->put_user(
        pubkey => $pk, group_id => 'g', target => $target_pk,
        previous => ['aabbccdd'],
    );
    my @prev = grep { $_->[0] eq 'previous' } @{$e->tags};
    is(scalar @prev, 1, 'one previous tag');
    is($prev[0][1], 'aabbccdd', 'value');
};

###############################################################################
# Passthrough args
###############################################################################

subtest 'extra args pass through to Event' => sub {
    my $e = Net::Nostr::Group->join_request(
        pubkey => $pk, group_id => 'g', created_at => 42,
    );
    is($e->created_at, 42, 'created_at');
};

done_testing;
