#!perl
use Cassandane::Tiny;
use Sub::Install;

### basic variable support
add_sieve_tests(
    "variables_set_get",
        'set "foo" "bar"; log "foo: ${foo}";',
        [
            [ 'log', {}, [ 'foo: bar' ] ],
            [ 'keep', {}, [] ],
        ],
);

### jmapquery
add_sieve_tests(
    "jmapquery_matches",
        'if jmapquery "{\"from\" : \"sam\"}" {
            fileinto "Matched";
        }',
        [ [ 'fileinto', {}, [ 'Matched' ] ] ],

    "jmapquery_does_not_match",
        'if jmapquery "{\"from\" : \"bugs\"}" {
            fileinto "Matched";
        }',
        [ [ 'keep', {}, [] ] ],
);

### fileinto
add_sieve_tests(
    "fileinto_basic",
        'fileinto "foo";',
        [ ['fileinto', {}, [ 'foo' ] ] ],

    "fileinto_specialuse",
        'fileinto :specialuse "\\\\junk" "Trash";',
        [ ['fileinto', { specialuse => '\junk' }, [ 'Trash' ] ] ],

    "fileinto_mailboxid",
        'fileinto :mailboxid "a-b-c-d" "Alphabet";',
        [ ['fileinto', { mailboxid => 'a-b-c-d' }, [ 'Alphabet' ] ] ],

    "fileinto_create",
        'fileinto :create "Server";',
        [ ['fileinto', { create => JSON::true }, [ 'Server' ] ] ],

    "fileinto_copy",
        'fileinto :copy "Paper";',
        [
            ['fileinto', { copy => JSON::true }, [ 'Paper' ] ],
            ['keep', {}, [] ],
        ],

    "fileinto_all",
        # (Can't use mailboxid and specialuse, so I picked one...)
        'fileinto :mailboxid "mbid" :create :copy "Woah";',
        [
            [
                'fileinto',
                {
                    mailboxid  => 'mbid',
                    create     => JSON::true,
                    copy       => JSON::true,
                },
                [ 'Woah' ],
            ],
            ['keep', {}, [] ],
        ],
);

### exists
add_sieve_tests(
    "exists",
        'if exists ["From"] { fileinto "FromHeaderFound"; }',
        [ ['fileinto', {}, [ 'FromHeaderFound' ] ] ],

    "exists_not",
        'if not exists ["From"] { fileinto "FromHeaderFound"; }',
        [ ['keep', {}, [] ] ],
);

### processimip
add_sieve_tests(
    "processimip_most_fields",
        'processimip
            :calendarid "a-b-c"
            :addresses ["a@b.com", "b@c.com" ]
            :organizers ":addrbook:Default"
            :deletecanceled;
        ',
        [
            [
                'processimip',
                {
                    allowpublic    => JSON::true,
                    deletecanceled => JSON::true,
                    calendarid     => "a-b-c",
                    addresses      => ['a@b.com', 'b@c.com'],
                    organizers     => ':addrbook:Default',
                },
                []
           ],
           [ 'keep', {}, [] ],
       ],

    "processimip_invitesonly",
        'processimip :invitesonly;',
        [
            [
                'processimip',
                {
                    allowpublic => JSON::true,
                    invitesonly => JSON::true,
                },
                []
            ],
            [ 'keep', {}, [] ],
        ],

    "processimip_updatesonly",
        'processimip :updatesonly;',
        [
            [
                'processimip',
                {
                    allowpublic => JSON::true,
                    updatesonly => JSON::true,
                },
                []
            ],
            [ 'keep', {}, [] ],
        ],
);

### duplicate
add_sieve_tests(
    "duplicate",
        'if duplicate { discard; }',
        [
            [ 'keep', {}, [] ],
        ],
);

### environment
add_sieve_tests(
    "environment",
        'if environment :contains "phase" "during" { discard; }',
        [
            [ 'discard', {}, [] ],
        ],
);

### someInThreadHaveKeyword
sub test_sieve_test_extensive_some_in_thread_have_keyword
    :min_version_3_3 :JMAPExtensions
    ($self)
{
    # Create two messages, where the first is muted and the second is in
    # the same conversation
    my $mid = '<cassandane@example.com>';

    my (undef, $email1) = $self->new_email_blob({
      'keywords'          => { '$muted' => JSON::true },
      'header:Message-Id' => $mid,
    });

    my ($email2_blob_id, $email2) = $self->new_email_blob({
      'header:inReplyTo'  => $mid,
      'header:references' => $mid,
    });

    $self->assert_str_equals($email1->{threadId}, $email2->{threadId});

    my $sieve_blob_id = $self->new_sieve_blob(
        'if jmapquery "{\"someInThreadHaveKeyword\":\"$muted\"}" {
             fileinto "muted";
        }',
    );

    $self->run_sieve_test(
        $sieve_blob_id,
        $email2_blob_id,
        [ ['fileinto', {}, ['muted'] ] ],
    );
}

sub add_sieve_tests (@tests)
{
    while (@tests) {
        my ($name, $sieve, $expect) = (
            shift @tests,
            shift @tests,
            shift @tests,
        );

        if (! $expect) {
            die "Expected at least 3 arguments, but didn't get it. Bad test spec?! "
                . Dumper({
                      name => $name,
                      sieve => $sieve,
                  });
        }

        my $opts;

        if (@tests && ref $tests[0] eq 'HASH') {
            $opts = shift @tests;
        }

        add_sieve_test($name, $sieve, $expect, $opts);
    }
}

sub add_sieve_test ($name, $sieve, $expect, $opts)
{

    unless ($name =~ /^[a-zA-Z][a-zA-Z0-9_]+$/) {
        die "Test name '$name' invalid. Must look like a perl subroutine name (start with a letter, a-zA-Z0-9_ only after that...\n";
    }

    Sub::Install::install_sub({
        code => sub :min_version_3_3 :JMAPExtensions {
            shift->_do_test($sieve, $expect, $opts);
        },
        as => "test_sieve_test_extensive_$name",
    });
}

sub _do_test ($self, $sieve, $expect, $opts)
{

    my $variables = $opts->{variables};
    my $email_set_params = $opts->{email_set_params};

    my $sieve_blob_id = $self->new_sieve_blob($sieve);
    my $email_blob_id = $self->new_email_blob($email_set_params);

    $self->run_sieve_test(
        $sieve_blob_id,
        $email_blob_id,
        $expect,
        $variables,
    );
}

sub new_sieve_blob ($self, $sieve)
{

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

    xlog "create script";
    my $fullscript = <<EOF;
require ["fileinto", "extlists", "imap4flags", "copy", "variables", "mailbox", "mailboxid", "special-use", "vnd.cyrus.log", "vnd.cyrus.jmapquery", "vnd.cyrus.imip", "duplicate", "environment"];

$sieve
EOF

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

    my $res = $jmap->CallMethods([
        ['Blob/upload', {
            create => {
               "A" => { data => [{'data:asText' => $fullscript}] }
            }
         }, "R0"],
        ['SieveScript/set', {
            create => {
                "1" => {
                    name => "foo",
                    blobId => "#A"
                }
            }
         }, "R1"]
    ]);
    $self->assert_not_null($res);

    my $script_blob_id = $res->[1][1]{created}{"1"}{blobId};
    $self->assert_not_null($script_blob_id);

    return $script_blob_id;
}

sub new_email_blob ($self, $email_set_params)
{

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

    xlog "create email";

    my $res = $jmap->CallMethods(
        [['Mailbox/get', { properties => ["id"] }, "R1"]]
    );
    my $inbox_id = $res->[0][1]{list}[0]{id};

    $self->assert_not_null($inbox_id);

    my $email =  {
        mailboxIds => { $inbox_id => JSON::true },
        from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] ,
        to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ],
        subject => "Memo",
        textBody => [{ partId => '1' }],
        bodyValues => { '1' => { value => "Whoa!" }},
        ( $email_set_params ? ( %$email_set_params ) : () ),
    };

    $res = $jmap->CallMethods([
        ['Email/set', { create => { "1" => $email }}, "R2"],
    ]);

    my $email_blob_id = $res->[0][1]{created}{"1"}{blobId};
    $self->assert_not_null($email_blob_id);

    return wantarray ? ($email_blob_id, $res->[0][1]{created}{"1"}) : $email_blob_id;
}

sub run_sieve_test ($self, $sieve_blob_id, $email_blob_id, $expect, $variables=undef)
{

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

    xlog "test script";
    my $res = $jmap->CallMethods([
        ['SieveScript/test', {
            scriptBlobId => "$sieve_blob_id",
            emailBlobIds => [ "$email_blob_id" ],
            envelope => JSON::null,
            lastVacationResponse => JSON::null,
            ( $variables ? (variables => $variables) : () ),
         }, "R3"]
    ]);
    $self->assert_not_null($res);
    $self->assert_not_null($res->[0][1]{completed});
    $self->assert_null($res->[0][1]{notCompleted});

    eval {
        $self->assert_deep_equals(
            $expect,
            $res->[0][1]{completed}{$email_blob_id},
        );
    };

    if ($@) {
        my $err = $@;

        warn "Wanted: " . Dumper($expect);
        warn "Got:    " . Dumper($res->[0][1]{completed}{$email_blob_id});

        # Rethrow for Test::Unit's sake
        die $@;
    }

    return $res;
}
