
import datetime as dt
from operator import itemgetter

import icalendar
import pkg_resources
import pytest
from khal.khalendar import backend
from khal.khalendar.exceptions import OutdatedDbVersionError, UpdateFailed

from .utils import BERLIN, LOCALE_BERLIN, _get_text

calname = 'home'


def test_new_db_version():
    dbi = backend.SQLiteDb(calname, ':memory:', locale=LOCALE_BERLIN)
    backend.DB_VERSION += 1
    with pytest.raises(OutdatedDbVersionError):
        dbi._check_table_version()


def test_event_rrule_recurrence_id():
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    assert dbi.list(calname) == list()
    events = dbi.get_localized(
        BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
        BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)),
    )
    assert list(events) == list()
    dbi.update(_get_text('event_rrule_recuid'), href='12345.ics', etag='abcd', calendar=calname)
    assert dbi.list(calname) == [('12345.ics', 'abcd')]
    events = dbi.get_localized(
        BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
        BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)),
    )
    events = sorted(events, key=itemgetter(2))
    assert len(events) == 6

    # start
    assert events[0][2] == BERLIN.localize(dt.datetime(2014, 6, 30, 7, 0))
    assert events[1][2] == BERLIN.localize(dt.datetime(2014, 7, 7, 9, 0))
    assert events[2][2] == BERLIN.localize(dt.datetime(2014, 7, 14, 7, 0))
    assert events[3][2] == BERLIN.localize(dt.datetime(2014, 7, 21, 7, 0))
    assert events[4][2] == BERLIN.localize(dt.datetime(2014, 7, 28, 7, 0))
    assert events[5][2] == BERLIN.localize(dt.datetime(2014, 8, 4, 7, 0))

    calendars = dbi.get_localized_calendars(
        BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
        BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)),
    )
    calendars = list(calendars)
    assert len(calendars) == 6


def test_event_rrule_recurrence_id_invalid_tzid():
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    dbi.update(_get_text('event_rrule_recuid_invalid_tzid'), href='12345.ics', etag='abcd',
               calendar=calname)
    events = dbi.get_localized(
        BERLIN.localize(dt.datetime(2014, 4, 30, 0, 0)),
        BERLIN.localize(dt.datetime(2014, 9, 26, 0, 0)))
    events = sorted(events, key=itemgetter(2))
    assert len(events) == 6

    assert events[0][2] == BERLIN.localize(dt.datetime(2014, 6, 30, 7, 0))
    assert events[1][2] == BERLIN.localize(dt.datetime(2014, 7, 7, 9, 0))
    assert events[2][2] == BERLIN.localize(dt.datetime(2014, 7, 14, 7, 0))
    assert events[3][2] == BERLIN.localize(dt.datetime(2014, 7, 21, 7, 0))
    assert events[4][2] == BERLIN.localize(dt.datetime(2014, 7, 28, 7, 0))
    assert events[5][2] == BERLIN.localize(dt.datetime(2014, 8, 4, 7, 0))


event_rrule_recurrence_id_reverse = """
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:event_rrule_recurrence_id
SUMMARY:Arbeit
RECURRENCE-ID:20140707T050000Z
DTSTART;TZID=Europe/Berlin:20140707T090000
DTEND;TZID=Europe/Berlin:20140707T140000
END:VEVENT
BEGIN:VEVENT
UID:event_rrule_recurrence_id
SUMMARY:Arbeit
RRULE:FREQ=WEEKLY;COUNT=6
DTSTART;TZID=Europe/Berlin:20140630T070000
DTEND;TZID=Europe/Berlin:20140630T120000
END:VEVENT
END:VCALENDAR
"""


def test_event_rrule_recurrence_id_reverse():
    """as icalendar elements can be saved in arbitrary order, we also have to
    deal with `reverse` ordered icalendar files
    """
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    assert dbi.list(calname) == list()
    events = dbi.get_localized(
        BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
        BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)))
    assert list(events) == list()
    dbi.update(event_rrule_recurrence_id_reverse, href='12345.ics', etag='abcd', calendar=calname)
    assert dbi.list(calname) == [('12345.ics', 'abcd')]
    events = dbi.get_localized(
        BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
        BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)))
    events = sorted(events, key=itemgetter(2))
    assert len(events) == 6

    assert events[0][2] == BERLIN.localize(dt.datetime(2014, 6, 30, 7, 0))
    assert events[1][2] == BERLIN.localize(dt.datetime(2014, 7, 7, 9, 0))
    assert events[2][2] == BERLIN.localize(dt.datetime(2014, 7, 14, 7, 0))
    assert events[3][2] == BERLIN.localize(dt.datetime(2014, 7, 21, 7, 0))
    assert events[4][2] == BERLIN.localize(dt.datetime(2014, 7, 28, 7, 0))
    assert events[5][2] == BERLIN.localize(dt.datetime(2014, 8, 4, 7, 0))


def test_event_rrule_recurrence_id_update_with_exclude():
    """
    test if updates work as they should. The updated event has the extra
    RECURRENCE-ID event removed and one recurrence date excluded via EXDATE
    """
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    dbi.update(_get_text('event_rrule_recuid'), href='12345.ics', etag='abcd', calendar=calname)
    dbi.update(_get_text('event_rrule_recuid_update'),
               href='12345.ics', etag='abcd', calendar=calname)
    events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 4, 30, 0, 0)),
                               BERLIN.localize(dt.datetime(2014, 9, 26, 0, 0)))
    events = sorted(events, key=itemgetter(2))
    assert len(events) == 5
    assert events[0][2] == BERLIN.localize(dt.datetime(2014, 6, 30, 7, 0))
    assert events[1][2] == BERLIN.localize(dt.datetime(2014, 7, 7, 7, 0))
    assert events[2][2] == BERLIN.localize(dt.datetime(2014, 7, 21, 7, 0))
    assert events[3][2] == BERLIN.localize(dt.datetime(2014, 7, 28, 7, 0))
    assert events[4][2] == BERLIN.localize(dt.datetime(2014, 8, 4, 7, 0))


def test_event_recuid_no_master():
    """
    test for events which have a RECUID component, but the master event is
    not present in the same file
    """
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    dbi.update(_get_text('event_dt_recuid_no_master'),
               href='12345.ics', etag='abcd', calendar=calname)
    events = dbi.get_floating(
        dt.datetime(2017, 3, 1, 0, 0), dt.datetime(2017, 4, 1, 0, 0),
    )
    events = sorted(events, key=itemgetter(2))
    assert len(events) == 1
    assert events[0][2] == dt.datetime(2017, 3, 29, 16)
    assert events[0][3] == dt.datetime(2017, 3, 29, 16, 25)
    assert 'SUMMARY:Infrastructure Planning' in events[0][0]


def test_event_recuid_rrule_no_master():
    """
    test for events which have a RECUID and a RRULE component, but the master event is
    not present in the same file
    """
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    dbi.update(
        _get_text('event_dt_multi_recuid_no_master'),
        href='12345.ics', etag='abcd', calendar=calname,
    )
    events = dbi.get_floating(
        dt.datetime(2010, 1, 1, 0, 0), dt.datetime(2020, 1, 1, 0, 0),
    )
    events = sorted(events, key=itemgetter(2))
    assert len(list(events)) == 2
    assert events[0][2] == dt.datetime(2014, 6, 30, 7, 30)
    assert events[0][3] == dt.datetime(2014, 6, 30, 12, 0)
    assert events[1][2] == dt.datetime(2014, 7, 7, 8, 30)
    assert events[1][3] == dt.datetime(2014, 7, 7, 12, 0)
    events = dbi.search('VEVENT')
    assert len(list(events)) == 2


def test_no_valid_timezone():
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    dbi.update(_get_text('event_dt_local_missing_tz'),
               href='12345.ics', etag='abcd', calendar=calname)
    events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 4, 9, 0, 0)),
                               BERLIN.localize(dt.datetime(2014, 4, 10, 0, 0)))
    events = sorted(list(events))
    assert len(events) == 1
    event = events[0]
    assert event[2] == BERLIN.localize(dt.datetime(2014, 4, 9, 9, 30))
    assert event[3] == BERLIN.localize(dt.datetime(2014, 4, 9, 10, 30))


def test_event_delete():
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    assert dbi.list(calname) == list()
    events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
                               BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)))
    assert list(events) == list()
    dbi.update(event_rrule_recurrence_id_reverse, href='12345.ics', etag='abcd', calendar=calname)
    assert dbi.list(calname) == [('12345.ics', 'abcd')]
    events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
                               BERLIN.localize(dt.datetime(2014, 9, 26, 0, 0)))
    assert len(list(events)) == 6
    dbi.delete('12345.ics', calendar=calname)
    events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
                               BERLIN.localize(dt.datetime(2014, 9, 26, 0, 0)))
    assert len(list(events)) == 0


event_rrule_this_and_prior = """
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:event_rrule_recurrence_id_this_and_prior
SUMMARY:Arbeit
RRULE:FREQ=WEEKLY;UNTIL=20140806T060000Z
DTSTART;TZID=Europe/Berlin:20140630T070000
DTEND;TZID=Europe/Berlin:20140630T120000
END:VEVENT
BEGIN:VEVENT
UID:event_rrule_recurrence_id_this_and_prior
SUMMARY:Arbeit
RECURRENCE-ID;RANGE=THISANDPRIOR:20140707T050000Z
DTSTART;TZID=Europe/Berlin:20140707T090000
DTEND;TZID=Europe/Berlin:20140707T140000
END:VEVENT
END:VCALENDAR
"""


def test_this_and_prior():
    """we do not support THISANDPRIOR, therefore this should fail"""
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    with pytest.raises(UpdateFailed):
        dbi.update(event_rrule_this_and_prior, href='12345.ics', etag='abcd', calendar=calname)


event_rrule_this_and_future_temp = """
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:event_rrule_recurrence_id
SUMMARY:Arbeit
RRULE:FREQ=WEEKLY;UNTIL=20140806T060000Z
DTSTART;TZID=Europe/Berlin:20140630T070000
DTEND;TZID=Europe/Berlin:20140630T120000
END:VEVENT
BEGIN:VEVENT
UID:event_rrule_recurrence_id
SUMMARY:Arbeit (lang)
RECURRENCE-ID;RANGE=THISANDFUTURE:20140707T050000Z
DTSTART;TZID=Europe/Berlin:{0}
DTEND;TZID=Europe/Berlin:{1}
END:VEVENT
END:VCALENDAR
"""

event_rrule_this_and_future = \
    event_rrule_this_and_future_temp.format('20140707T090000', '20140707T180000')


def test_event_rrule_this_and_future():
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    dbi.update(event_rrule_this_and_future, href='12345.ics', etag='abcd', calendar=calname)
    assert dbi.list(calname) == [('12345.ics', 'abcd')]
    events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 4, 30, 0, 0)),
                               BERLIN.localize(dt.datetime(2014, 9, 26, 0, 0)))
    events = sorted(events, key=itemgetter(2))
    assert len(events) == 6

    assert events[0][2] == BERLIN.localize(dt.datetime(2014, 6, 30, 7, 0))
    assert events[1][2] == BERLIN.localize(dt.datetime(2014, 7, 7, 9, 0))
    assert events[2][2] == BERLIN.localize(dt.datetime(2014, 7, 14, 9, 0))
    assert events[3][2] == BERLIN.localize(dt.datetime(2014, 7, 21, 9, 0))
    assert events[4][2] == BERLIN.localize(dt.datetime(2014, 7, 28, 9, 0))
    assert events[5][2] == BERLIN.localize(dt.datetime(2014, 8, 4, 9, 0))

    assert events[0][3] == BERLIN.localize(dt.datetime(2014, 6, 30, 12, 0))
    assert events[1][3] == BERLIN.localize(dt.datetime(2014, 7, 7, 18, 0))
    assert events[2][3] == BERLIN.localize(dt.datetime(2014, 7, 14, 18, 0))
    assert events[3][3] == BERLIN.localize(dt.datetime(2014, 7, 21, 18, 0))
    assert events[4][3] == BERLIN.localize(dt.datetime(2014, 7, 28, 18, 0))
    assert events[5][3] == BERLIN.localize(dt.datetime(2014, 8, 4, 18, 0))

    assert 'SUMMARY:Arbeit\n' in events[0][0]
    for num, event in enumerate(events[1:]):
        assert 'SUMMARY:Arbeit (lang)\n' in event[0]


event_rrule_this_and_future_multi_day_shift = \
    event_rrule_this_and_future_temp.format('20140708T090000', '20140709T150000')


def test_event_rrule_this_and_future_multi_day_shift():
    dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
    dbi.update(event_rrule_this_and_future_multi_day_shift,
               href='12345.ics', etag='abcd', calendar=calname)
    assert dbi.list(calname) == [('12345.ics', 'abcd')]
    events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 4, 30, 0, 0)),
                               BERLIN.localize(dt.datetime(2014, 9, 26, 0, 0)))
    events = sorted(events, key=itemgetter(2))
    assert len(events) == 6

    assert events[0][2] == BERLIN.localize(dt.datetime(2014, 6, 30, 7, 0))
    assert events[1][2] == BERLIN.localize(dt.datetime(2014, 7, 8, 9, 0))
    assert events[2][2] == BERLIN.localize(dt.datetime(2014, 7, 15, 9, 0))
    assert events[3][2] == BERLIN.localize(dt.datetime(2014, 7, 22, 9, 0))
    assert events[4][2] == BERLIN.localize(dt.datetime(2014, 7, 29, 9, 0))
    assert events[5][2] == BERLIN.localize(dt.datetime(2014, 8, 5, 9, 0))

    assert events[0][3] == BERLIN.localize(dt.datetime(2014, 6, 30, 12, 0))
    assert events[1][3] == BERLIN.localize(dt.datetime(2014, 7, 9, 15, 0))