#!perl
use Cassandane::Tiny;

sub test_email_legacy_ids
    :MailboxLegacyDirs :NoAltNameSpace :Conversations :JMAPExtensions :NoStartInstances
    ($self)
{
    $self->{instance}->{config}->set(mboxlist_db => 'twoskip');

    $self->_start_instances();
    $self->_setup_http_service_objects();
    $self->setup_default_using();

    my $jmap = $self->{jmap};

    my $store = $self->{store};
    my $talk = $store->get_client();
    $talk->create('INBOX.foo');

    my $data_file = abs_path("data/old-mailboxes/version19.tar.gz");
    die "Old mailbox data does not exist: $data_file" if not -f $data_file;

    xlog "installing version 19 mailboxes";
    $self->{instance}->unpackfile($data_file, $self->{instance}->get_basedir());

    xlog "reconstructing indexes at v19 to get predictable senddate";
    $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', '-G', '-q');

    xlog $self, "Fetching EMAILIDs";
    $talk->examine('INBOX');
    my $res = $talk->fetch('1:*', '(UID EMAILID THREADID DIGEST.SHA1 SAVEDATE INTERNALDATE)');
    my $id1 = $res->{1}{emailid}[0];
    my $id2 = $res->{2}{emailid}[0];
    my $id3 = $res->{3}{emailid}[0];
    my $id4 = $res->{4}{emailid}[0];
    my $thrid1 = $res->{1}{threadid}[0];
    my $thrid2 = $res->{2}{threadid}[0];
    my $blobid4 = $res->{4}{'digest.sha1'};

    $self->assert_matches(qr/^M/, $id1);
    $self->assert_matches(qr/^M/, $id2);
    $self->assert_matches(qr/^M/, $id3);
    $self->assert_matches(qr/^M/, $id4);
    $self->assert_matches(qr/^T/, $thrid1);
    $self->assert_matches(qr/^T/, $thrid2);
    $self->assert_equals($thrid1, $res->{3}{threadid}[0]);
    $self->assert_equals($thrid1, $res->{4}{threadid}[0]);

    xlog $self, "get email list";
    $res = $jmap->CallMethods([['Email/query', {
        sort => [{
            property => 'receivedAt',
        }],
    }, "R1"]]);
    $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}});
    $self->assert_str_equals($id2, $res->[0][1]->{ids}[0]);
    $self->assert_str_equals($id1, $res->[0][1]->{ids}[1]);
    my $state = $res->[0][1]->{queryState};
    $self->assert_matches(qr/^[0-9].*/, $state);

    xlog $self, "get emails";
    my $ids = $res->[0][1]->{ids};
    $res = $jmap->CallMethods([['Email/get', { ids => $ids }, "R1"]]);
    $self->assert_num_equals(4, scalar @{$res->[0][1]->{list}});
    my $msg = $res->[0][1]->{list}[0];
    $self->assert_str_equals($id2, $msg->{id});
    $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}});
    $self->assert_matches(qr/^[0-9].*/, $res->[0][1]->{'state'});

    xlog $self, "get threads";
    $res = $jmap->CallMethods([['Thread/get',
                                { ids => [ $thrid1, $thrid2 ] }, "R1"]]);
    # We don't need to sort the arrays because the order is consistent
    # due to the files being part of a tarball and therefore static ids
    $self->assert_num_equals(2, scalar @{$res->[0][1]->{list}});
    $self->assert_str_equals($thrid1, $res->[0][1]->{list}[0]{id});
    $self->assert_num_equals(3, scalar @{$res->[0][1]->{list}[0]{emailIds}});
    $self->assert_str_equals($id1, $res->[0][1]->{list}[0]{emailIds}[0]);
    $self->assert_str_equals($id3, $res->[0][1]->{list}[0]{emailIds}[1]);
    $self->assert_str_equals($id4, $res->[0][1]->{list}[0]{emailIds}[2]);
    $self->assert_str_equals($thrid2, $res->[0][1]->{list}[1]{id});
    $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}[1]{emailIds}});
    $self->assert_str_equals($id2, $res->[0][1]->{list}[1]{emailIds}[0]);

    xlog $self, "Append an old email from gmail.com";
    # This will create a v4 G record in conv.db
    # (nanoseconds-based internaldate), but should still sort BEFORE
    # the existing v3 G records (seconds-based internaldate)
    my $email = <<EOF;
Subject: foo
Date: bar
From: <foobar\@gmail.com>

Body
EOF

    $email =~ s/\r?\n/\r\n/gs;

    $talk->append("INBOX", "()", " 7-Feb-1994 22:43:04 -0800",
                  { Literal => "$email" });
    $res = $talk->fetch('5', '(UID EMAILID THREADID DIGEST.SHA1)');
    my $id5 = $res->{5}{emailid}[0];
    $self->assert_matches(qr/^M/, $id5);

    xlog $self, "run squatter";
    $self->{instance}->run_command({cyrus => 1}, 'squatter');

    xlog $self, "query emails from gmail.com";
    $res = $jmap->CallMethods([['Email/query', {
        filter => {
            from => 'gmail.com',
        },
        sort => [{
            property => 'receivedAt',
        }],
    }, "R1"]]);
    $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}});
    $self->assert_str_equals($id5, $res->[0][1]->{ids}[0]);
    $self->assert_str_equals($id3, $res->[0][1]->{ids}[1]);
    $self->assert_str_equals($id4, $res->[0][1]->{ids}[2]);
    $state = $res->[0][1]->{queryState};
    $self->assert_matches(qr/^[0-9].*/, $state);

    xlog $self, "Update $id2";
    $res = $jmap->CallMethods([['Email/set', {
        update => {
            $id2 => {
                keywords => {
                    '$flagged' => JSON::true
                }
            }
        }
    }, "R1"]]);
    $self->assert(exists $res->[0][1]{updated}{$id2});
    $self->assert_matches(qr/^[0-9].*/, $res->[0][1]->{oldState});
    $self->assert_matches(qr/^[0-9].*/, $res->[0][1]->{newState});

    xlog $self, "get email list updates";
    $res = $jmap->CallMethods([['Email/queryChanges', {
        sinceQueryState => $state,
        sort => [{
            property => 'receivedAt',
        }],
    }, "R1"]]);
    $self->assert_num_equals(5, $res->[0][1]->{total});
    $self->assert_matches(qr/^[0-9].*/, $res->[0][1]->{oldQueryState});
    $self->assert_matches(qr/^[0-9].*/, $res->[0][1]->{newQueryState});

    xlog $self, "Destroy $id3";
    $res = $jmap->CallMethods([['Email/set', {
        destroy => [ $id3 ]
    }, "R1"]]);
    $self->assert_str_equals($id3, $res->[0][1]{destroyed}[0]);

    xlog $self, "Create user and share mailbox read-write";
    $self->{instance}->create_user("other");
    my $admintalk = $self->{adminstore}->get_client();
    $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die;

    my $dstInboxId = $self->getinbox({accountId => 'other'})->{id};
    $self->assert_not_null($dstInboxId);

    xlog $self, "Enable compactids";
    $self->{instance}->run_command({ cyrus => 1 },
                                   'ctl_conversationsdb', '-I', 'on', 'other');

    my $auth = $jmap->get_client_session;

    my $session = $auth->client_session;
    my $capabilities = $session->{accounts}->{cassandane}->{accountCapabilities};
    $self->assert_deep_equals(
        {
            maxKeywordsPerEmail => 100,
            hasCompactIds => JSON::false
        },
        $capabilities->{'https://cyrusimap.org/ns/jmap/mail'});
    $capabilities = $session->{accounts}->{other}->{accountCapabilities};
    $self->assert_deep_equals(
        {
            maxKeywordsPerEmail => 100,
            hasCompactIds => JSON::true
        },
        $capabilities->{'https://cyrusimap.org/ns/jmap/mail'});

    xlog $self, "Create new email";
    $res = $jmap->CallMethods([
        ['Email/set', {
            create => {
                1 => {
                    mailboxIds => {
                        '$inbox' => JSON::true
                    },
                    from => [{ email => q{foo@bar} }],
                    to => [{ email => q{bar@foo} }],
                    subject => "test",
                }
            },
        }, 'R1'],
    ]);
    my $id6 = $res->[0][1]->{created}{1}{id};
    $self->assert_matches(qr/^M/, $id6);
    $state = $res->[0][1]->{newState};

    xlog $self, "Move $id4";
    $res = $jmap->CallMethods([
        ['Email/copy', {
            fromAccountId => 'cassandane',
            ifFromInState => $state,
            accountId => 'other',
            create => {
                1 => {
                    id => $id4,
                    mailboxIds => {
                        $dstInboxId => JSON::true,
                    },
                },
            },
            onSuccessDestroyOriginal => JSON::true,
        }, 'R1'],
    ]);
    $self->assert_matches(qr/^S/, $res->[0][1]->{created}{1}{id});
    $self->assert_str_equals("G$blobid4", $res->[0][1]->{created}{1}{blobId});
    $self->assert_str_equals($id4, $res->[1][1]{destroyed}[0]);
    $self->assert_matches(qr/^J/, $res->[0][1]->{oldState});
    $self->assert_matches(qr/^J/, $res->[0][1]->{newState});
    $self->assert_matches(qr/^[0-9].*/, $res->[1][1]->{oldState});
    $self->assert_matches(qr/^[0-9].*/, $res->[1][1]->{newState});

    # make the consistency check happy
    $self->{instance}->run_command({ cyrus => 1 },
                                   'reconstruct', '-G', '-q', '-u', 'other');
}
