#!perl
use Cassandane::Tiny;
use Cyrus::IndexFile;

# This tests both a bit of the interface of Cyrus::IndexFile and also that
# cyrus itself is writing out index files correctly according to the
# definitions Cyrus::IndexFile expects - to make sure that Cyrus itself isn't
# writing/reading incorrect index files consistently.
sub test_cyrus_indexfile_19
    :MailboxVersion(19)
    ($self)
{
    my $jmap = $self->{jmap};

    my $res = $jmap->CallMethods([[
        'Mailbox/set' => {
            create => {
                "1" => {
                    name     => "foo",
                },
            },
        }, "a",
    ]]);

    $self->assert_str_equals('Mailbox/set', $res->[0][0]);

    my $mailbox_id = $res->[0][1]{created}{1}{id};
    $self->assert_not_null($mailbox_id);

    my $created_modseq = $res->[0][1]{newState};
    $self->assert_not_null($created_modseq);

    $created_modseq =~ s/^J//;

    # Our base assumptions. Will be overridden below as things change
    my %header_expect = (
        Answered          => 0,
        Deleted           => 0,
        DeletedModseq     => 0,
        Exists            => 0,
        FirstExpunged     => 0,
        Flagged           => 0,
        Format            => 0,
        Generation        => 0,
        LastAppenddate    => 0,
        LastCleanup       => 0,
        LastUid           => 0,
        LeakedCache       => 0,
        MinorVersion      => 19,
        NumRecords        => 0,
        Options           => '00000000000000000000000000000001',
        Pop3LastLogin     => 0,
        Pop3ShowAfter     => 0,
        QuotaAnnotUsed    => 0,
        QuotaDeletedUsed  => 0,
        QuotaExpungedUsed => 0,
        QuotaUsed         => 0,
        RecentTime        => 0,
        RecentUid         => 0,
        Unseen            => 0,

        CreatedModseq     => $created_modseq,
        HighestModseq     => $created_modseq,
        UidValidity       => re('\A[1-9][0-9]+\z'),
        ChangesEpoch      => num(time, 60),

        HeaderCrc         => ignore(),
        HeaderFileCRC     => ignore(),
        SyncCRCsAnnot     => ignore(),
        SyncCRCsBasic     => ignore(),

        RecordSize        => 112,
        StartOffset       => 160,
    );

    xlog $self, "Checking index for newly created folder";
    {
        my $index = $self->index_file_for('user.cassandane.foo');
        $self->assert_cmp_deeply(\%header_expect, $index->header_copy);

        my @recs = $self->index_file_records($index);
        $self->assert_num_equals(0, 0+@recs);
    }

    xlog $self, "Appending a message then checking again";
    $self->{store}->set_folder('foo');

    my $msg = $self->make_message('A message');

    $res = $jmap->CallMethods([
        [
            'Email/query' => {
                filter => {
                    inMailbox => $mailbox_id,
                },
            }, 'a',
        ], [
            'Email/get' => {
                '#ids' => {
                    resultOf => 'a',
                    name     => 'Email/query',
                    path     => '/ids',
                },
            }, 'b',
        ],
    ]);

    my $email_id = $res->[0][1]{ids}[0];
    $self->assert_not_null($email_id);

    my $cid = $res->[1][1]{list}[0]{threadId} =~ s/^T//r;
    $self->assert_not_null($cid);

    my $guid = $res->[1][1]{list}[0]{blobId} =~ s/^G//r;
    $self->assert_not_null($guid);

    my %record_expect = (
        CID           => $cid,
        CreatedModseq => $created_modseq + 1,
        MessageGuid   => $guid,
        Modseq        => $created_modseq + 1,
        Size          => length $msg->as_string,
        SystemFlags   => {},
        Uid           => 1,
        UserFlags     => '0' x 128,

        GmTime        => num(time, 60),
        InternalDate  => num(time, 60),
        LastUpdated   => num(time, 60),
        SaveDate      => num(time, 60),
        SentDate      => any(
            $self->sentdate_ts(time), # Start of today
            $self->sentdate_ts(time) - 86400, # Start of yesterday
        ),

        CacheCrc      => ignore(),
        RecordCrc     => ignore(),
        CacheOffset   => ignore(),
        HeaderSize    => ignore(),
        CacheVersion  => ignore(),
    );

    xlog $self, "Checking index for newly created message";
    {
        %header_expect = (
            %header_expect,

            HighestModseq  => $created_modseq + 1,
            LastAppenddate => num(time, 60),
            LastUid        => 1,
            RecentTime     => num(time, 60),
            RecentUid      => 1,
            Exists         => 1,
            NumRecords     => 1,
            Unseen         => 1,
            QuotaUsed      => length $msg->as_string,
        );

        my $index = $self->index_file_for('user.cassandane.foo');
        $self->assert_cmp_deeply(\%header_expect, $index->header_copy);

        my @recs = $self->index_file_records($index);
        $self->assert_num_equals(1, 0+@recs);

        $self->assert_cmp_deeply(\%record_expect, $recs[0]);
    }

    xlog $self, "Marking a message answered then checking again";
    $res = $jmap->CallMethods([[
        "Email/set" => {
            update => {
                $email_id => {
                    keywords => {
                        '$seen' => JSON::true,
                    },
                },
            },
        }, 'a',
    ]]);

    $self->assert_not_null($res->[0][1]{updated});

    {
        %header_expect = (
            %header_expect,

            HighestModseq => $created_modseq + 2,
            Unseen        => 0,
        );

        my $index = $self->index_file_for('user.cassandane.foo');
        $self->assert_cmp_deeply(\%header_expect, $index->header_copy);

        my @recs = $self->index_file_records($index);
        $self->assert_num_equals(1, 0+@recs);

        %record_expect = (
            %record_expect,

            Modseq        => $created_modseq + 2,
            SystemFlags   => { '\\Seen' => 1 },
        );

        $self->assert_cmp_deeply(\%record_expect, $recs[0]);
    }

    xlog $self, "Deleting a message then checking again";
    $res = $jmap->CallMethods([[
        'Email/set' => {
            destroy => [ $email_id ]
        }, 'a',
    ]]);

    $self->assert_str_equals($email_id, $res->[0][1]{destroyed}[0]);

    {
        %header_expect = (
            %header_expect,

            HighestModseq     => $created_modseq + 3,
            QuotaUsed         => 0,
            FirstExpunged     => num(time, 60),
            Exists            => 0,
            QuotaExpungedUsed => length $msg->as_string,

        );

        my $index = $self->index_file_for('user.cassandane.foo');
        $self->assert_cmp_deeply(\%header_expect, $index->header_copy);

        my @recs = $self->index_file_records($index);
        $self->assert_num_equals(1, 0+@recs);

        %record_expect = (
            %record_expect,

            Modseq        => $created_modseq + 3,
            SystemFlags   => {
                '\\Seen'     => 1,
                '\\Deleted'  => 1, # new
                '[EXPUNGED]' => 1, # new
            },
        );

        $self->assert_cmp_deeply(\%record_expect, $recs[0]);
    }

    xlog $self, "Check offsets";

    my $index = $self->index_file_for('user.cassandane.foo');

    # Some header fields
    $self->assert_num_equals(0, $index->header_offset_for('Generation'));
    $self->assert_num_equals(28, $index->header_offset_for('LastUid'));
    $self->assert_num_equals(156, $index->header_offset_for('HeaderCrc'));

    # Some record fields
    $self->assert_num_equals(0, $index->record_offset_for('Uid'));
    $self->assert_num_equals(8, $index->record_offset_for('SentDate'));
    $self->assert_num_equals(108, $index->record_offset_for('RecordCrc'));

    xlog $self, "Check times";

    # A header field, and some special/non-special record fields
    my %expect = map {
        (
            $_         => num(time, 60),
            $_ . "_ns" => num(0),
        )
    } qw(RecentTime InternalDate SaveDate);

    $expect{SentDate} = any(
        $self->sentdate_ts(time), # Start of today
        $self->sentdate_ts(time) - 86400, # Start of yesterday
    );
    $expect{SentDate_ns} = 0;

    my @recs = $self->index_file_records($index);
    $self->assert_num_equals(1, 0+@recs);

    my $rec = $recs[0];

    my %got = (
        RecentTime    => $index->tv_sec($index->header_copy->{RecentTime}),
        RecentTime_ns => $index->tv_nsec($index->header_copy->{RecentTime}),
    );

    for my $rfield (qw(InternalDate SaveDate SentDate)) {
        $got{$rfield} = $index->tv_sec($rec->{$rfield});
        $got{$rfield . '_ns'} = $index->tv_nsec($rec->{$rfield});
    }

    $self->assert_cmp_deeply(\%expect, \%got);
}
