#!perl
use Cassandane::Tiny;

sub test_calendarevent_defaultalerts_calalarmd
    :min_version_3_9 :needs_component_calalarmd
{
    my ($self) = @_;

    my $jmap = $self->{jmap};
    my $caldav = $self->{caldav};
    my $now = DateTime->now();
    $now->set_time_zone('Australia/Sydney');

    xlog $self, "Create calendar without default alarms";
    my $res = $jmap->CallMethods([
        ['Calendar/set', {
            create => {
                1 => {
                    name => 'test',
                },
            }
        }, 'R1'],
    ]);
    my $calendarId = $res->[0][1]{created}{1}{id};
    $self->assert_not_null($calendarId);

    xlog $self, "Remove any default alarms";
    $res = $jmap->CallMethods([
        ['Calendar/set', {
            update => {
                $calendarId => {
                    defaultAlertsWithTime => undef,
                },
            },
        }, 'R1'],
    ]);
    $self->assert(exists $res->[0][1]{updated}{$calendarId});

    xlog $self, "Create event that starts in an hour, ocurring daily";
    my $startdt = $now->clone();
    $startdt->add(DateTime::Duration->new(hours => 1));
    my $start = $startdt->strftime('%Y%m%dT%H%M%S');

    my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9";
    my $href = "$calendarId/$uuid.ics";
    my $card = <<EOF;
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:Australia/Sydney
BEGIN:STANDARD
DTSTART:19700101T000000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZOFFSETFROM:+1100
TZOFFSETTO:+1000
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19700101T000000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=10
TZOFFSETFROM:+1000
TZOFFSETTO:+1100
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20150806T234327Z
UID:574E2CD0-2D2A-4554-8B63-C7504481D3A9
TRANSP:OPAQUE
SUMMARY:Simple
DTSTART;TZID=Australia/Sydney:$start
DURATION:PT15S
RRULE:FREQ=DAILY
DTSTAMP:20150806T234327Z
SEQUENCE:0
X-JMAP-USEDEFAULTALERTS;VALUE=BOOLEAN:TRUE
BEGIN:VALARM
UID:e01ddb42-f2f1-4e39-9d94-17fcc5aa320c
TRIGGER:-PT30M
ACTION:DISPLAY
SUMMARY:Hello
END:VALARM
END:VEVENT
END:VCALENDAR
EOF

    $caldav->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar');

    xlog $self, "Assert that useDefaultAlerts got rewritten to false";
    $res = $jmap->CallMethods([
        ['CalendarEvent/get', {
            properties => ['useDefaultAlerts'],
        }, 'R1'],
    ]);
    $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{useDefaultAlerts});
    my $eventId = $res->[0][1]{list}[0]{id};

    # clean notification cache
    $self->{instance}->getnotify();

    xlog "Custom alarm triggers half an hour before event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800);
    $self->assert_alarms({summary => 'Simple', start => $start});

    xlog $self, "Set default alarms to trigger at start of event";
    $res = $jmap->CallMethods([
        ['Calendar/set', {
            update => {
                $calendarId => {
                    defaultAlertsWithTime => {
                        alert1 => {
                            '@type' => 'Alert',
                            trigger => {
                                '@type' => 'OffsetTrigger',
                                relativeTo => 'start',
                                offset => 'PT0S',
                            },
                            action => 'display',
                        },
                    }
                },
            }
        }, 'R1'],
    ]);
    $self->assert(exists $res->[0][1]{updated}{$calendarId});

    xlog $self, "Set useDefaultAlerts=true for event";
    $res = $jmap->CallMethods([
        ['CalendarEvent/set', {
            update => {
                $eventId => {
                    useDefaultAlerts => JSON::true,
                }
            },
        }, 'R1'],
    ]);
    $self->assert(exists $res->[0][1]{updated}{$eventId});

    xlog $self, "Forward clock by one day";
    $now->add(DateTime::Duration->new(days => 1));
    $startdt->add(DateTime::Duration->new(days => 1));
    $start = $startdt->strftime('%Y%m%dT%H%M%S');
    $start = $startdt->strftime('%Y%m%dT%H%M%S');

    # clean notification cache
    $self->{instance}->getnotify();

    xlog "Custom alarm does not trigger half an hour before event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800);
    $self->assert_alarms();

    xlog "Default alarm triggers at start of event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch);
    $self->assert_alarms({summary => 'Simple', start => $start});

    xlog $self, "Update default alarms to trigger half an hour after event";
    $res = $jmap->CallMethods([
        ['Calendar/set', {
            update => {
                $calendarId => {
                    defaultAlertsWithTime => {
                        alert2 => {
                            '@type' => 'Alert',
                            trigger => {
                                '@type' => 'OffsetTrigger',
                                relativeTo => 'start',
                                offset => 'PT30M',
                            },
                            action => 'display',
                        },
                    }
                },
            }
        }, 'R1'],
    ]);
    $self->assert(exists $res->[0][1]{updated}{$calendarId});

    xlog $self, "Forward clock by one day";
    $now->add(DateTime::Duration->new(days => 1));
    $startdt->add(DateTime::Duration->new(days => 1));
    $start = $startdt->strftime('%Y%m%dT%H%M%S');

    xlog "Custom alarm does not trigger half an hour before event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800);
    $self->assert_alarms();

    xlog "Former default alarm does not trigger at start of event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch);
    $self->assert_alarms();

    xlog "Current default alarm gets triggered half an hour after the event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch + 1800 );
    $self->assert_alarms({summary => 'Simple', start => $start});

    xlog $self, "Forward clock by one day";
    $now->add(DateTime::Duration->new(days => 1));
    $startdt->add(DateTime::Duration->new(days => 1));
    $start = $startdt->strftime('%Y%m%dT%H%M%S');

    xlog $self, "Remove default alarms again";
    $res = $jmap->CallMethods([
        ['Calendar/set', {
            update => {
                $calendarId => {
                    defaultAlertsWithTime => undef,
                },
            },
        }, 'R1'],
    ]);
    $self->assert(exists $res->[0][1]{updated}{$calendarId});

    xlog "Custom alarm does not trigger half an hour before event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800);
    $self->assert_alarms();

    xlog "Former default alarm does not trigger at start of event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch);
    $self->assert_alarms();

    xlog "Former default alarm does not trigger half an hour after the event";
    $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch + 1800 );
    $self->assert_alarms();
}

sub _can_match_a {
    my $event = shift;
    my $want = shift;

    # I wrote a really good one of these for Caldav, but this will do for now
    foreach my $key (keys %$want) {
        return 0 if not exists $event->{$key};
        return 0 if $event->{$key} ne $want->{$key};
    }

    return 1;
}


sub assert_alarms {
    my $self = shift;
    my @want = @_;
    # pick first calendar alarm from notifications
    my $data = $self->{instance}->getnotify();
    if ($self->{replica}) {
        my $more = $self->{replica}->getnotify();
        push @$data, @$more;
    }
    my @events;
    foreach (@$data) {
        if ($_->{CLASS} eq 'EVENT') {
            my $e = decode_json($_->{MESSAGE});
            if ($e->{event} eq "CalendarAlarm") {
                push @events, $e;
            }
        }
    }

    my @left;
    while (my $event = shift @events) {
        my $found = 0;
        my @newwant;
        foreach my $data (@want) {
            if (not $found and _can_match_a($event, $data)) {
                $found = 1;
            }
            else {
                push @newwant, $data;
            }
        }
        if (not $found) {
            push @left, $event;
        }
        @want = @newwant;
    }

    if (@want or @left) {
        my $dump = Data::Dumper->Dump([\@want, \@left], [qw(want left)]);
        $self->assert_equals(0, scalar @want,
                             "expected events were not received:\n$dump");
        $self->assert_equals(0, scalar @left,
                             "unexpected extra events were received:\n$dump");
    }
}

