import datetime
from unittest import mock

import pytest
import vobject
from django.utils.html import escape

from eo_gestion.eo_conges.views import ReportError

from .utils import login


@pytest.fixture
def events():
    with mock.patch('caldav.davclient.DAVClient.principal') as mocked_principal:
        mocked_principal.side_effet = FakePrincipal()
        yield


def get_event(summary, start, end, category='Congés'):
    event = vobject.newFromBehavior('vevent')
    event = event.transformToNative()
    event.add('summary').value = summary
    event.add('dtstart').value = start
    event.add('dtend').value = end
    event.add('categories').value = [category]
    return event


def test_conges_redirect(app, settings):
    login(app)
    resp = app.get('/conges/')
    assert resp.location == '/conges/2019/1/'


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]
    events = mocked_get_events.return_value = []

    login(app)
    resp = app.get('/conges/2023/01/')
    assert [x.text for x in resp.pyquery('.worker span')] == [
        'Jean Dupont (matricule 42)',
        'Tickets restaurant : 19 tickets de 8 euros',
        'Congés : 0.',
    ]

    assert '/conges/2022/12/' in resp.text
    assert '/conges/2023/2/' in resp.text

    events.append(
        get_event('Jean', datetime.datetime(2023, 1, 2, 8, 00), datetime.datetime(2023, 1, 2, 18, 00))
    )

    resp = app.get('/conges/2023/01/')
    assert [x.text for x in resp.pyquery('.worker span')] == [
        'Jean Dupont (matricule 42)',
        'Tickets restaurant : 19 tickets de 8 euros',
        'Congés : le 02/01.',
    ]

    events += [
        # one day, using dates
        get_event('Jean', datetime.date(2023, 1, 4), datetime.date(2023, 1, 5)),
        # two days range, using mixed date/datetimes
        get_event('Jean', datetime.datetime(2023, 1, 10, 8, 00), datetime.datetime(2023, 1, 11)),
        # two days range, using dates
        get_event('Jean', datetime.date(2023, 1, 12), datetime.date(2023, 1, 14)),
        # 4 days range, using dates
        get_event('Jean', datetime.date(2023, 1, 16), datetime.date(2023, 1, 20)),
        # only morning
        get_event('Jean', datetime.datetime(2023, 1, 23, 8, 00), datetime.datetime(2023, 1, 23, 14, 00)),
        # only afternoon
        get_event('Jean', datetime.datetime(2023, 1, 24, 14, 00), datetime.datetime(2023, 1, 24, 18, 00)),
    ]

    # 6 days range, using rrule
    event = get_event('Jean', datetime.date(2023, 1, 25), datetime.date(2023, 1, 26))
    event.add('rrule').value = 'FREQ=DAILY;UNTIL=20230131'
    events.append(event)

    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == (
        'Congés : le 02/01, le 04/01, les 10 et 11/01, les 12 et 13/01, du 16 au 19/01, 0,5j le 23/01, '
        '0,5j le 24/01, du 25 au 31/01.'
    )


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_fatal_errors(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]
    mocked_get_events.side_effect = ReportError('Error xxx')

    login(app)
    resp = app.get('/conges/2023/01/')
    assert 'Error xxx' in resp.text

    app.get('/conges/2023/42/', status=404)


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_multiple_match_error(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
        {'username': 'jdurand', 'display_name': 'Jean Durand', 'number': 43},
    ]

    mocked_get_events.return_value = [
        get_event('Jean', datetime.date(2023, 1, 6), datetime.date(2023, 1, 10)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert (
        escape("L'évènement Jean correspond à plusieurs coopérateurices : jdurand et jdupont.") in resp.text
    )


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_no_match_error(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]

    mocked_get_events.return_value = [
        get_event('Jean', datetime.date(2023, 1, 6), datetime.date(2023, 1, 7)),
        get_event('xxx', datetime.date(2023, 1, 8), datetime.date(2023, 1, 9)),
        get_event('yyy', datetime.date(2023, 1, 8), datetime.date(2023, 1, 9)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert escape('Aucun·e coopérateurice trouvé·e pour les évènements : xxx, yyy') in resp.text
    assert 'le 06/01' in resp.text


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_day_off_error(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]
    mocked_get_events.return_value = []

    login(app)
    resp = app.get('/conges/2023/01/')
    assert escape("Jean Dupont n'a pas de jour off.") in resp.text

    ev1 = get_event('Jean off', datetime.date(2023, 1, 6), datetime.date(2023, 1, 7), category='Journées off')
    ev1.add('rrule').value = 'FREQ=WEEKLY;BYDAY=MO'
    ev2 = get_event(
        'Jean off 2', datetime.date(2023, 1, 8), datetime.date(2023, 1, 9), category='Journées off'
    )
    ev2.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'
    mocked_get_events.return_value = [ev1, ev2]

    resp = app.get('/conges/2023/01/')
    assert 'Jean Dupont a trop de jours off : Jean off, Jean off 2.' in resp.text

    ev1.dtstart.value = datetime.datetime(2023, 1, 6, 8, 0)
    ev1.dtend.value = datetime.datetime(2023, 1, 6, 13, 0)
    ev2.dtstart.value = datetime.datetime(2023, 1, 9, 14, 0)
    ev2.dtend.value = datetime.datetime(2023, 1, 9, 19, 0)

    resp = app.get('/conges/2023/01/')
    assert 'Jean Dupont a trop de jours off' not in resp.text

    ev3 = get_event(
        'Jean off 3',
        datetime.datetime(2023, 1, 9, 14, 0),
        datetime.datetime(2023, 1, 10, 19, 0),
        category='Journées off',
    )
    ev3.add('rrule').value = 'FREQ=WEEKLY;BYDAY=TH'
    mocked_get_events.return_value.append(ev3)

    resp = app.get('/conges/2023/01/')
    assert 'Jean Dupont a trop de jours off : Jean off, Jean off 2, Jean off 3.' in resp.text


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_is_parisian(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]
    mocked_get_events.return_value = []

    login(app)
    resp = app.get('/conges/2023/01/')
    assert 'Carte transport Paris 50%' not in resp.text

    settings.WORKERS_CALENDAR_CONFIG['workers'][0]['is_parisian'] = True

    resp = app.get('/conges/2023/01/')
    assert 'Carte transport Paris 50%' in resp.text


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_ignored_workers(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
        {'username': 'lmichel', 'display_name': 'Louise Michel', 'number': 0, 'ignore': True},
    ]
    mocked_get_events.return_value = [
        get_event('congés jdupont', datetime.date(2023, 1, 2), datetime.date(2023, 1, 3)),
        get_event('congés lmichel', datetime.date(2023, 1, 3), datetime.date(2023, 1, 4)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert 'Jean' in resp.text
    assert 'Louise' not in resp.text


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_worker_possible_names(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
        {'username': 'jmartin', 'display_name': 'Jean Martin', 'number': 43, 'aliases': ['Jeannot']},
    ]

    mocked_get_events.return_value = [
        get_event('congés jdupont', datetime.date(2023, 1, 2), datetime.date(2023, 1, 3)),
        get_event('Jeannot en vacances', datetime.date(2023, 1, 3), datetime.date(2023, 1, 4)),
        get_event('congés JD', datetime.date(2023, 1, 5), datetime.date(2023, 1, 6)),
        get_event('JeanM', datetime.date(2023, 1, 6), datetime.date(2023, 1, 7)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert [x.text for x in resp.pyquery('.worker span.holidays')] == [
        'Congés : le 02/01, le 05/01.',
        'Congés : le 03/01, le 06/01.',
    ]


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_strip_weekend(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]

    mocked_get_events.return_value = [
        # starts friday, ends monday
        get_event('Jean', datetime.date(2023, 1, 6), datetime.date(2023, 1, 10)),
        # start saturday, ends sunday next week
        get_event('Jean', datetime.date(2023, 1, 14), datetime.date(2023, 1, 23)),
        # two days of weekend
        get_event('Jean', datetime.date(2023, 1, 28), datetime.date(2023, 1, 30)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 06 au 09/01, du 16 au 20/01.'


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_month_span(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]

    mocked_get_events.return_value = [
        # starts in december
        get_event('Jean', datetime.date(2022, 12, 25), datetime.date(2023, 1, 5)),
        # ends in february
        get_event('Jean', datetime.date(2023, 1, 30), datetime.date(2023, 2, 10)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 02 au 04/01, les 30 et 31/01.'


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_split_on_day_off(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]

    holidays = get_event('Jean', datetime.date(2023, 1, 2), datetime.date(2023, 1, 7))
    events = mocked_get_events.return_value = [holidays]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 02 au 06/01.'

    # off on wednesday, holidays split in half
    event = get_event('Jean', datetime.date(2023, 1, 4), datetime.date(2023, 1, 5), category='Journées off')
    event.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'
    events.append(event)

    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : les 02 et 03/01, les 05 et 06/01.'

    # off on friday, holidays end one day sooner
    event.rrule.value = 'FREQ=WEEKLY;BYDAY=FR'

    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 02 au 05/01.'

    # off on monday, holidays start one day later
    event.rrule.value = 'FREQ=WEEKLY;BYDAY=MO'

    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 03 au 06/01.'

    # long holidays get split multiple times
    holidays.dtend.value = datetime.date(2023, 1, 21)

    resp = app.get('/conges/2023/01/')
    assert (
        resp.pyquery('.worker span.holidays').text()
        == 'Congés : du 03 au 06/01, du 10 au 13/01, du 17 au 20/01.'
    )

    # split on wednesday
    event.rrule.value = 'FREQ=WEEKLY;BYDAY=WE'

    resp = app.get('/conges/2023/01/')
    assert (
        resp.pyquery('.worker span.holidays').text()
        == 'Congés : les 02 et 03/01, du 05 au 10/01, du 12 au 17/01, les 19 et 20/01.'
    )


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_split_on_multiple_days_off(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]

    holidays = get_event('Jean', datetime.date(2023, 1, 2), datetime.date(2023, 1, 7))
    events = mocked_get_events.return_value = [holidays]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 02 au 06/01.'

    ev1 = get_event(
        'Jean off lundi matin',
        datetime.datetime(2023, 1, 6, 8, 0),
        datetime.datetime(2023, 1, 6, 13, 0),
        category='Journées off',
    )
    ev1.add('rrule').value = 'FREQ=WEEKLY;BYDAY=MO'
    ev2 = get_event(
        'Jean off mercredi aprem',
        datetime.datetime(2023, 1, 9, 14, 0),
        datetime.datetime(2023, 1, 9, 19, 0),
        category='Journées off',
    )
    ev2.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'
    mocked_get_events.return_value += [ev1, ev2]

    resp = app.get('/conges/2023/01/')
    assert (
        resp.pyquery('.worker span.holidays').text()
        == 'Congés : 0,5j le 02/01, le 03/01, 0,5j le 04/01, les 05 et 06/01.'
    )

    # long holidays get split multiple times
    holidays.dtend.value = datetime.date(2023, 1, 14)

    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == (
        'Congés : 0,5j le 02/01, le 03/01, 0,5j le 04/01, les 05 et 06/01, 0,5j le 09/01, '
        'le 10/01, 0,5j le 11/01, les 12 et 13/01.'
    )


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_split_on_day_off_single_day(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]

    # off on wednesday
    off_event = get_event(
        'Jean', datetime.date(2023, 1, 4), datetime.date(2023, 1, 5), category='Journées off'
    )
    off_event.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'

    mocked_get_events.return_value = [
        off_event,
        # holidays on monday
        get_event('Jean', datetime.date(2023, 1, 2), datetime.date(2023, 1, 3)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : le 02/01.'

    # off on monday
    off_event.rrule.value = 'FREQ=WEEKLY;BYDAY=MO'

    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : 0.'


@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_split_on_multiple_days_off_single_day(mocked_get_events, app, settings):
    settings.WORKERS_CALENDAR_CONFIG['workers'] = [
        {'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
    ]

    ev1 = get_event(
        'Jean off lundi matin',
        datetime.datetime(2023, 1, 6, 8, 0),
        datetime.datetime(2023, 1, 6, 13, 0),
        category='Journées off',
    )
    ev1.add('rrule').value = 'FREQ=WEEKLY;BYDAY=MO'
    ev2 = get_event(
        'Jean off mercredi aprem',
        datetime.datetime(2023, 1, 9, 14, 0),
        datetime.datetime(2023, 1, 9, 19, 0),
        category='Journées off',
    )
    ev2.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'

    mocked_get_events.return_value = [
        ev1,
        ev2,
        # holidays on monday
        get_event('Jean', datetime.date(2023, 1, 2), datetime.date(2023, 1, 3)),
    ]

    login(app)
    resp = app.get('/conges/2023/01/')
    assert resp.pyquery('.worker span.holidays').text() == 'Congés : 0,5j le 02/01.'
