import json
import os
from unittest import mock

import pytest
from django.test import override_settings
from django.utils.dateparse import parse_date

import tests.utils
from passerelle.apps.maelis.models import Link, Maelis
from passerelle.apps.maelis.utils import (
    decompose_event,
    get_school_year,
    month_range,
    week_boundaries_datetimes,
)

pytestmark = pytest.mark.django_db

TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'maelis')


def get_xml_file(filename):
    with open(os.path.join(TEST_BASE_DIR, filename), 'rb') as desc:
        return desc.read()


def get_json_file(filename):
    with open(os.path.join(TEST_BASE_DIR, "%s.json" % filename)) as fd:
        return json.load(fd)


@pytest.fixture
def family_service_wsdl():
    return get_xml_file('FamilyService.wsdl')


@pytest.fixture
def invoice_service_wsdl():
    return get_xml_file('InvoiceService.wsdl')


@pytest.fixture
def activity_service_wsdl():
    return get_xml_file('ActivityService.wsdl')


@pytest.fixture
def catalog_mocked_get(activity_service_wsdl, family_service_wsdl):
    return (
        tests.utils.FakedResponse(
            content=family_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'}
        ),
        tests.utils.FakedResponse(
            content=activity_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'}
        ),
    )


@pytest.fixture
def catalog_mocked_post():
    return (
        tests.utils.FakedResponse(
            content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'}
        ),
        tests.utils.FakedResponse(
            content=get_xml_file('readActivityListResponse.xml'),
            status_code=200,
            headers={'Content-Type': 'text/xml'},
        ),
    )


@pytest.fixture
def connector(db):
    return tests.utils.setup_access_rights(
        Maelis.objects.create(
            slug='test', login='foo', password='bar', base_url='http://www3.sigec.fr/entrouvertws/services/'
        )
    )


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_link_unlink(mocked_post, mocked_get, family_service_wsdl, connector, app):
    mocked_get.return_value = mock.Mock(content=family_service_wsdl)
    mocked_post.return_value = mock.Mock(
        content=get_xml_file('readFamilyByPasswordError.xml'),
        status_code=500,
        headers={'Content-Type': 'text/xml'},
    )
    assert Link.objects.count() == 0
    resp = app.post_json(
        '/maelis/test/link?NameID=local',
        params={'family_id': '3264', 'password': 'wrong', 'school_year': '2020'},
    )
    assert resp.json['err'] == 1
    assert resp.json['err_desc'] == 'E204 : Le mot de passe est incorrect'
    assert not resp.json['data']
    assert Link.objects.count() == 0

    resp = app.post_json('/maelis/test/unlink?NameID=local')
    assert resp.json['err']
    assert resp.json['err_desc'] == 'User not linked to family'

    mocked_post.return_value = mock.Mock(
        content=get_xml_file('readFamilyByPasswordResult.xml'),
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    resp = app.post_json('/maelis/test/link?NameID=local', params={'family_id': '3264', 'password': '123456'})
    assert resp.json['err'] == 0
    assert resp.json['data']
    assert resp.json['data']['number'] == 3264
    assert Link.objects.count() == 1
    link = Link.objects.get()
    assert link.name_id == 'local'
    assert link.family_id == '3264'

    resp = app.post_json('/maelis/test/unlink?NameID=local')
    assert resp.json['err'] == 0
    assert Link.objects.count() == 0


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_family_info(mocked_post, mocked_get, family_service_wsdl, connector, app):
    mocked_get.return_value = mock.Mock(content=family_service_wsdl)
    mocked_post.return_value = mock.Mock(
        content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'}
    )
    assert Link.objects.count() == 0
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    resp = app.get('/maelis/test/family-info?NameID=local')
    assert resp.json['data']["number"] == 3264
    assert resp.json['data']['rl1InfoBean']
    assert resp.json['data']['childInfoList']

    resp = app.get('/maelis/test/children-info?NameID=local')
    assert resp.json['data']
    for child in resp.json['data']:
        assert child['id']
        assert child['text']

    resp = app.get('/maelis/test/child-info?NameID=local&childID=21293')
    assert resp.json['data']
    assert resp.json['data']['num'] == '21293'
    assert resp.json['data']['id'] == '21293'

    resp = app.get('/maelis/test/adults-info?NameID=local')
    assert resp.json['data']
    for child in resp.json['data']:
        assert child['id']
        assert child['text']


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_activity_list(mocked_post, mocked_get, catalog_mocked_get, catalog_mocked_post, connector, app):
    mocked_get.side_effect = catalog_mocked_get
    mocked_post.side_effect = catalog_mocked_post
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    resp = app.get('/maelis/test/activity-list?NameID=local&personID=21293')
    assert resp.json['data']
    for activity in resp.json['data']:
        assert activity['id']
        assert activity['text']


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_update_coordinates(mocked_post, mocked_get, family_service_wsdl, connector, app):
    mocked_get.return_value = mock.Mock(content=family_service_wsdl)
    mocked_post.side_effect = (
        tests.utils.FakedResponse(
            content=get_xml_file('updateCoordinatesResponse.xml'),
            status_code=200,
            headers={'Content-Type': 'text/xml'},
        ),
        tests.utils.FakedResponse(
            content=get_xml_file('updateCoordinatesError.xml'),
            status_code=200,
            headers={'Content-Type': 'text/xml'},
        ),
    )
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    resp = app.post_json(
        '/maelis/test/update-coordinates?NameID=local&personID=21293', params={'mail': 'foo@example.com'}
    )
    assert resp.content is not None

    resp = app.post_json(
        '/maelis/test/update-coordinates?NameID=local&personID=21293', params={'town': 'Paris', 'num': '169'}
    )
    assert resp.json['err']
    assert resp.json['err_desc'] == 'E16 : Le code postal est obligatoire'


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_list_invoices(mocked_post, mocked_get, invoice_service_wsdl, connector, app):
    mocked_get.return_value = mock.Mock(content=invoice_service_wsdl)
    mocked_post.return_value = mock.Mock(
        content=get_xml_file('readInvoicesResponse.xml'),
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    resp = app.get('/maelis/test/regie/1/invoices?NameID=local')
    assert resp.json['data']
    for invoice in resp.json['data']:
        assert invoice['display_id']
        assert invoice['label']
        assert invoice['total_amount']
        assert not invoice['paid']


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_get_invoice_details(mocked_post, mocked_get, invoice_service_wsdl, connector, app):
    mocked_get.return_value = mock.Mock(content=invoice_service_wsdl)
    mocked_post.return_value = mock.Mock(
        content=get_xml_file('readInvoicesResponse.xml'),
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    resp = app.get('/maelis/test/regie/1/invoice/3264-53186?NameID=local')
    assert resp.json['data']
    assert resp.json['data']['label'] == 'FACTURATION SEPTEMBRE 2014'
    assert resp.json['data']['display_id'] == '53186'


@pytest.mark.parametrize(
    'date, schoolyear',
    [
        ('2020-07-30', 2019),
        ('2020-07-31', 2020),
    ],
)
def test_get_school_year(date, schoolyear):
    date = parse_date(date)
    assert schoolyear == get_school_year(date)


@pytest.mark.parametrize(
    'date, monday, sunday',
    [
        ('2020-12-27', '2020-12-21', '2020-12-27'),
        ('2020-12-28', '2020-12-28', '2021-01-03'),
        ('2020-12-31', '2020-12-28', '2021-01-03'),
        ('2021-01-01', '2020-12-28', '2021-01-03'),
        ('2021-01-03', '2020-12-28', '2021-01-03'),
    ],
)
def test_week_boundaries(date, monday, sunday):
    start, end = week_boundaries_datetimes(date)
    assert start.strftime('%Y-%m-%d') == monday
    assert end.strftime('%Y-%m-%d') == sunday


@pytest.mark.parametrize(
    'start, end, items',
    [
        ('2020-10-22', '2020-12-06', ['2020-10-01', '2020-11-01', '2020-12-01']),
        ('2020-12-31', '2021-01-03', ['2020-12-01', '2021-01-01']),
        ('2020-12-28', '2020-12-28', ['2020-12-01']),
        ('2021-01-03', '2020-12-01', []),
    ],
)
def test_month_range(start, end, items):
    start = parse_date(start)
    end = parse_date(end)
    assert [x.strftime('%Y-%m-%d') for x in month_range(start, end)] == items


def test_decompose_event():
    resp = get_json_file('child_planning_before_decomposition')
    data = resp['data']
    assert len(data) == 43
    assert data[22]['text'] == 'JOURNEE'
    assert data[23]['text'] == 'MATIN'
    assert data[24]['text'] == 'MATIN ET REPAS'
    assert data[25]['text'] == 'APRES MIDI'
    assert not [x for x in data if x['text'] == 'REPAS']

    # unit is break down into its components
    assert [x['text'] for x in decompose_event(data[22])] == ['Matinée', 'Repas', 'Après-midi']
    assert [x['text'] for x in decompose_event(data[23])] == ['Matinée']
    assert [x['text'] for x in decompose_event(data[24])] == ['Matinée', 'Repas']
    assert [x['text'] for x in decompose_event(data[25])] == ['Après-midi']

    # child_planning function use a dict to remove dupplicated components
    data = {x['id']: x for e in data for x in decompose_event(e)}.values()
    assert len(data) == 42
    assert len([x for x in data if x['text'] == 'Repas']) == 1
    assert len([x for x in data if 'EO0001' in x['slot_id']]) == 3


@override_settings(TIME_ZONE='Europe/Paris')
@pytest.mark.parametrize(
    'legacy, nb_events, nb_booked, response',
    [
        ('please', 43, 9, 'child_planning_before_decomposition'),
        ('', 42, 9, 'child_planning_after_decomposition'),
    ],
)
@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_get_child_planning(
    mocked_post,
    mocked_get,
    legacy,
    nb_events,
    nb_booked,
    response,
    family_service_wsdl,
    activity_service_wsdl,
    app,
    connector,
):
    mocked_get.side_effect = (
        tests.utils.FakedResponse(
            content=family_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'}
        ),
        tests.utils.FakedResponse(
            content=activity_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'}
        ),
        tests.utils.FakedResponse(
            content=activity_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'}
        ),
    )
    mocked_post.side_effect = (
        tests.utils.FakedResponse(
            content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'}
        ),
        tests.utils.FakedResponse(
            content=get_xml_file('child_planning_readActivityListResponse.xml'),
            status_code=200,
            headers={'Content-Type': 'text/xml'},
        ),
        tests.utils.FakedResponse(
            content=get_xml_file('child_planning_readChildMonthPlanningResponse.xml'),
            status_code=200,
            headers={'Content-Type': 'text/xml'},
        ),
    )
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    url = '/maelis/test/child-planning?NameID=local&childID=21293&start_date=2020-12-19'
    url += '&legacy=' + legacy
    resp = app.get(url)
    data = resp.json['data']
    previous_event = {}
    for event in data:
        assert event['category']
        assert event['text']
        if previous_event:
            assert previous_event['id'] <= event['id']
        previous_event = event
    assert len(data) == nb_events
    assert len([s for s in data if s['user_booking_status'] == 'booked']) == nb_booked
    assert resp.json == get_json_file(response)


@pytest.mark.parametrize(
    'parameters, nb_subscribed, nb_not_subscribed',
    [
        ('&subscribingStatus=', 2, 18),
        ('&subscribingStatus=subscribed', 2, 0),
        ('&subscribingStatus=not-subscribed', 0, 17),
        ('&subscribePublication=ELN', 2, 48),
    ],
)
@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_child_activities(
    mocked_post,
    mocked_get,
    parameters,
    nb_subscribed,
    nb_not_subscribed,
    catalog_mocked_get,
    catalog_mocked_post,
    connector,
    app,
):
    mocked_get.side_effect = catalog_mocked_get
    mocked_post.side_effect = catalog_mocked_post
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    url = '/maelis/test/child-activities?NameID=local&childID=21293&queryDate=2020-12-15'
    url += parameters
    resp = app.get(url)
    if parameters == '&subscribingStatus=':
        assert resp.json == get_json_file('child_activities')
    status = [x['user_subscribing_status'] for x in resp.json['data']]
    assert len([x for x in status if x == 'subscribed']) == nb_subscribed
    assert len([x for x in status if x == 'not-subscribed']) == nb_not_subscribed


@pytest.mark.parametrize(
    'parameters, err_desc',
    [
        ('&childID=99999', 'Child not found'),
        ('&subscribingStatus=not-a-status', 'wrong value for subscribingStatus'),
        ("&queryDate=2020-02-31", 'not a valid date'),
        ("&queryDate=not-a-date", 'YYYY-MM-DD expected'),
    ],
)
@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_child_activities_errors(
    mocked_post, mocked_get, parameters, err_desc, catalog_mocked_get, catalog_mocked_post, connector, app
):
    mocked_get.side_effect = catalog_mocked_get
    mocked_post.side_effect = catalog_mocked_post
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    url = '/maelis/test/child-activities?NameID=local&childID=21293'
    url += parameters
    resp = app.get(url)
    assert resp.json['err']
    assert err_desc in resp.json['err_desc']


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_subscribe(mocked_post, mocked_get, family_service_wsdl, connector, app):
    mocked_get.return_value = mock.Mock(content=family_service_wsdl)
    mocked_post.side_effect = (
        tests.utils.FakedResponse(
            content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'}
        ),
        tests.utils.FakedResponse(
            content=get_xml_file('subscribeActivityResult.xml'),
            status_code=200,
            headers={'Content-Type': 'text/xml'},
        ),
    )
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    resp = app.get(
        '/maelis/test/subscribe/?NameID=local&childID=21293'
        + '&activityID=A10003123507&unitID=A10003123507&placeID=A10000000211'
        + '&weeklyPlanning=XX1XX11&start_date=2020-08-01&end_date=2021-07-31'
    )
    assert not resp.json['err']
    assert resp.json['data']['state'] == {"idState": "1", "isWaitState": False, "libelle": "Confirmé"}


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_unsubscribe(mocked_post, mocked_get, family_service_wsdl, connector, app):
    mocked_get.return_value = mock.Mock(content=family_service_wsdl)
    mocked_post.side_effect = (
        tests.utils.FakedResponse(
            content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'}
        ),
        tests.utils.FakedResponse(
            content=get_xml_file('deletesubscribe.xml'), status_code=200, headers={'Content-Type': 'text/xml'}
        ),
    )
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    resp = app.get(
        '/maelis/test/unsubscribe/?NameID=local&childID=21293'
        + '&activityID=A10003121692&start_date=2020-08-01'
    )
    assert not resp.json['err']
    assert resp.json['data'] is None


@pytest.mark.parametrize(
    'parameters, nb_bus_lines',
    [
        ('&direction=', 2),
        ('&direction=Aller', 1),
        ('&direction=retour', 1),
    ],
)
@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_bus_lines(
    mocked_post, mocked_get, parameters, nb_bus_lines, catalog_mocked_get, catalog_mocked_post, connector, app
):
    mocked_get.side_effect = catalog_mocked_get
    mocked_post.side_effect = catalog_mocked_post
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    url = '/maelis/test/bus-lines?NameID=local&childID=21293'
    url += '&activityID=A10003131850&unitID=A10003131903&queryDate=2020-12-15'
    url += parameters
    resp = app.get(url)
    if parameters == '&direction=':
        assert resp.json == get_json_file('bus_lines')
    assert len(resp.json['data']) == nb_bus_lines


@pytest.mark.parametrize(
    'parameters, err_desc',
    [
        ('&childID=99999', 'Child not found'),
        ('&queryDate=2020-02-31', 'not a valid date'),
        ('&queryDate=not-a-date', 'YYYY-MM-DD expected'),
        ('&direction=heaven', 'wrong value for direction'),
    ],
)
@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_bus_lines_errors(
    mocked_post, mocked_get, parameters, err_desc, catalog_mocked_get, catalog_mocked_post, connector, app
):
    mocked_get.side_effect = catalog_mocked_get
    mocked_post.side_effect = catalog_mocked_post
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    url = '/maelis/test/bus-lines?NameID=local&childID=21293&activityID=1&unitID=2'
    url += parameters
    resp = app.get(url)
    assert resp.json['err']
    assert err_desc in resp.json['err_desc']


@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_bus_stops(mocked_post, mocked_get, catalog_mocked_get, catalog_mocked_post, connector, app):
    mocked_get.side_effect = catalog_mocked_get
    mocked_post.side_effect = catalog_mocked_post
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    url = '/maelis/test/bus-stops?NameID=local&childID=21293'
    url += '&busActivityID=A10003151396&busUnitID=A10003151402&queryDate=2020-12-15'
    resp = app.get(url)
    assert resp.json == get_json_file('bus_stops')
    assert len(resp.json['data']) == 6


@pytest.mark.parametrize(
    'parameters, err_desc',
    [
        ('&childID=99999', 'Child not found'),
        ('&queryDate=2020-02-31', 'not a valid date'),
        ('&queryDate=not-a-date', 'YYYY-MM-DD expected'),
        ('&busActivityID=1', 'Bus activity not found: 1'),
        ('&busUnitID=2', 'Bus unit not found: 2'),
    ],
)
@mock.patch('passerelle.utils.Request.get')
@mock.patch('passerelle.utils.Request.post')
def test_bus_stops_errors(
    mocked_post, mocked_get, parameters, err_desc, catalog_mocked_get, catalog_mocked_post, connector, app
):
    mocked_get.side_effect = catalog_mocked_get
    mocked_post.side_effect = catalog_mocked_post
    Link.objects.create(resource=connector, family_id='3264', name_id='local')
    url = '/maelis/test/bus-stops?NameID=local&childID=21293'
    url += '&busActivityID=A10003151396&busUnitID=A10003151402'
    url += parameters
    resp = app.get(url)
    assert resp.json['err']
    assert err_desc in resp.json['err_desc']
