import collections
from datetime import datetime
from unittest import mock

import pytest
import requests
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from httmock import HTTMock, urlmatch

from passerelle.base.models import AccessRight, ApiUser
from passerelle.contrib.planitech import mste
from passerelle.contrib.planitech.models import Pairing, PlanitechConnector, get_extensions
from passerelle.utils.jsonresponse import APIError


def assert_mste(data, ref_data):
    """skip CRC verification"""
    assert len(data) == len(ref_data)
    for i, v in enumerate(data):
        if i != 2:
            assert v == ref_data[i]


@pytest.mark.parametrize(
    "data,mste_data",
    [
        (None, ["MSTE0102", 6, "CRC82413E70", 0, 0, 0]),
        ("toto", ["MSTE0102", 7, "CRCD45ACB10", 0, 0, 21, "toto"]),  # string
        (mste.Couple(("toto", "tata")), ["MSTE0102", 10, "CRCD45ACB10", 0, 0, 32, 21, "toto", 21, "tata"]),
        # couple
        (
            [mste.Couple(("toto", "tata")), mste.Couple(("toto", "tata"))],
            ["MSTE0102", 14, "CRCD45ACB10", 0, 0, 31, 2, 32, 21, "toto", 21, "tata", 9, 1],
        ),
        # couple are stored in refs
        (["toto"], ["MSTE0102", 9, "CRCD4E14B75", 0, 0, 31, 1, 21, "toto"]),  # array
        (
            ["toto", "tata", "toto"],
            ["MSTE0102", 13, "CRC7311752F", 0, 0, 31, 3, 21, "toto", 21, "tata", 9, 1],
        ),  # array with reference
        ({"mykey": "toto"}, ["MSTE0102", 11, "CRC1C9E9FE1", 0, 1, "mykey", 30, 1, 0, 21, "toto"]),
        # dictionnary
        (
            [{"mykey": "toto"}, {"mykey": "toto"}],
            ["MSTE0102", 15, "CRC1C9E9FE1", 0, 1, "mykey", 31, 2, 30, 1, 0, 21, "toto", 9, 1],
        ),
        # dictionnary are stored in refs
        (float(2), ["MSTE0102", 7, "CRC1C9E9FE1", 0, 0, 20, 2]),  # decimal
        ([float(2), float(2)], ["MSTE0102", 11, "CRC1C9E9FE1", 0, 0, 31, 2, 20, 2, 9, 1]),
        # decimal are stored in refs
        (mste.Uint32(1), ["MSTE0102", 7, "CRC1C9E9FE1", 0, 0, 15, 1]),  # uint32
        (True, ["MSTE0102", 6, "CRC1C9E9FE1", 0, 0, 1]),  # True
        (False, ["MSTE0102", 6, "CRC1C9E9FE1", 0, 0, 2]),  # False
        ('', ["MSTE0102", 6, "CRC1C9E9FE1", 0, 0, 3]),  # empty string
        (datetime.fromtimestamp(1537364340), ["MSTE0102", 7, "CRC1C9E9FE1", 0, 0, 22, 1537364340]),
        # local date
        (
            [datetime.fromtimestamp(1537364340), datetime.fromtimestamp(1537364340)],
            ["MSTE0102", 11, "CRC1C9E9FE1", 0, 0, 31, 2, 22, 1537364340, 9, 1],
        ),
        # local date in refs
    ],
)
def test_mste(mste_data, data):
    assert data == mste.decode(mste_data)
    assert_mste(mste.encode(data), mste_data)


def test_encode_unsupported_type():
    with pytest.raises(TypeError):
        mste.encode(set())


def test_real():
    mste_data = [
        "MSTE0102",
        128,
        "CRC99D9BCEB",
        0,
        11,
        "requestDate",
        "responseDate",
        "requestName",
        "requestedEndingTime",
        "availablePlaces",
        "label",
        "freeGaps",
        "placeIdentifier",
        "resourceIdentifier",
        "daysMask",
        "requestedStartingTime",
        30,
        7,
        0,
        22,
        1538404500,
        1,
        22,
        1538404500,
        2,
        21,
        "getFreeGaps",
        3,
        20,
        600,
        4,
        31,
        1,
        30,
        4,
        5,
        21,
        "M.F.F. 2",
        6,
        31,
        15,
        32,
        22,
        1538384400,
        22,
        1538388000,
        32,
        22,
        1538470800,
        22,
        1538474400,
        32,
        22,
        1538557200,
        22,
        1538560800,
        32,
        22,
        1538643600,
        22,
        1538647200,
        32,
        22,
        1538989200,
        22,
        1538992800,
        32,
        22,
        1539075600,
        22,
        1539079200,
        32,
        22,
        1539162000,
        22,
        1539165600,
        32,
        22,
        1539248400,
        22,
        1539252000,
        32,
        22,
        1539334800,
        22,
        1539338400,
        32,
        22,
        1539507600,
        22,
        1539511200,
        32,
        22,
        1539594000,
        22,
        1539597600,
        32,
        22,
        1539680400,
        22,
        1539684000,
        32,
        22,
        1539766800,
        22,
        1539770400,
        32,
        22,
        1539853200,
        22,
        1539856800,
        32,
        22,
        1539939600,
        22,
        1539943200,
        7,
        20,
        2,
        8,
        9,
        54,
        9,
        20,
        127,
        10,
        20,
        540,
    ]
    mste.decode(mste_data)


@pytest.fixture()
def connector(db):
    api = ApiUser.objects.create(username='all', keytype='', key='')
    connector = PlanitechConnector.objects.create(
        url='http://example.planitech.com/',
        username='admin',
        password='admin',
        verify_cert=False,
        slug='slug-planitech',
        custom_fields={'places': [{'name': 'some_custom_field', 'type': 'string'}]},
        price_code='T1',
    )
    obj_type = ContentType.objects.get_for_model(connector)
    AccessRight.objects.create(
        codename='can_access', apiuser=api, resource_type=obj_type, resource_pk=connector.pk
    )
    return connector


def mock_planitech(monkeypatch, return_value=None, side_effect=None, referential=None):
    from passerelle.contrib.planitech import models

    monkeypatch.setattr(models.PlanitechConnector, '_login', mock.Mock())
    kwargs = {}
    if return_value is not None:
        kwargs['return_value'] = return_value
    if side_effect is not None:
        kwargs['side_effect'] = side_effect
    mock_call_planitech = mock.Mock(**kwargs)
    monkeypatch.setattr(models.PlanitechConnector, '_call_planitech', mock_call_planitech)

    if referential is not None:
        mock_get_referential = mock.Mock(return_value=referential)
        monkeypatch.setattr(models.PlanitechConnector, '_raw_get_places_referential', mock_get_referential)

    return mock_call_planitech


def test_call_planitech(connector, monkeypatch):
    class MockResponse:

        status_code = 200
        content = None

        def __init__(self, content=None, status_code=None):
            if content is not None:
                self.content = content
            if status_code is not None:
                self.status_code = status_code

        def session_meth(self, *args, **kwargs):
            return self

        def json(self):
            return mste.encode(self.content)

    connector._planitech_session = True

    response = MockResponse(content='somestring')
    assert connector._call_planitech(response.session_meth, 'endpoint') == "somestring"

    response = MockResponse(content=set(), status_code=400)
    with pytest.raises(APIError) as excinfo:
        connector._call_planitech(response.session_meth, 'endpoint')
    assert str(excinfo.value) == 'Planitech error 400'

    response = MockResponse(content='unexpected error format', status_code=400)
    with pytest.raises(APIError) as excinfo:
        connector._call_planitech(response.session_meth, 'endpoint')
    assert str(excinfo.value) == 'Planitech error 400'

    response = MockResponse(content={'errors': 'planitech error message'}, status_code=400)
    with pytest.raises(APIError) as excinfo:
        connector._call_planitech(response.session_meth, 'endpoint')
    assert str(excinfo.value) == 'Planitech error 400 - planitech error message'


def test_create_reservation(app, connector, monkeypatch):
    side_effect = [
        {
            'creationStatus': 'OK',
            'externalUserIdentifier': '22b9c0d91fdc4f379d1356a4aaa9d38b',
            'requestDate': datetime(2019, 1, 9, 15, 41),
            'requestName': 'createPerson',
            'responseDate': datetime(2019, 1, 9, 15, 41),
            'userIdentifier': 13826.0,
        },
        {'creationStatus': 'OK', 'reservationIdentifier': 1},
    ]
    mock_call_planitech = mock_planitech(monkeypatch, side_effect=side_effect)
    params = {
        'date': '2018-11-11',
        'start_time': '10:00',
        'end_time': '11:00',
        'place_id': 1,
        'price': 10,
        'name_id': 'john-doe',
        'first_name': 'jon',
        'last_name': 'doe',
        'email': 'jon.doe@localhost',
        'activity_id': 1,
        'object': 'reservation object',
        'type_id': 1,
        'vat_rate': 1,
    }

    response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
    json_resp = response.json
    assert json_resp['err'] == 0
    assert json_resp['data']['reservation_id'] == 1

    person_args = mock_call_planitech.call_args_list[0][0]
    assert person_args[1] == 'createPerson'
    person_args = person_args[2]
    assert person_args['mail'] == 'jon.doe@localhost'
    assert person_args['firstName'] == 'jon'
    assert person_args['name'] == 'doe'
    assert person_args['pricingCode'] == 'T1'
    external_id = person_args['externalUserIdentifier']
    pairing = Pairing.objects.get(resource=connector, name_id='john-doe')
    assert pairing.external_id == external_id
    assert pairing.price_code == 'T1'

    reservation_args = mock_call_planitech.call_args_list[1][0]
    assert reservation_args[1] == 'createReservation'
    reservation_args = reservation_args[2]
    assert reservation_args['start'] == datetime(2018, 11, 11, 10, 0)
    assert reservation_args['end'] == datetime(2018, 11, 11, 11, 0)
    assert reservation_args['places'] == [1]
    assert reservation_args['price'] == 10
    assert reservation_args['contractorExternalIdentifier'] == pairing.external_id
    assert reservation_args['activityID'] == 1
    assert reservation_args['object'] == 'reservation object'
    assert reservation_args['typeID'] == 1
    assert reservation_args['vatRate'] == 1

    # Second reservation for same user : no planitech person created
    mock_call_planitech = mock_planitech(monkeypatch, return_value=side_effect[1])
    response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
    json_resp = response.json
    assert json_resp['err'] == 0
    assert json_resp['data']['reservation_id'] == 1

    reservation_args = mock_call_planitech.call_args_list[0][0]
    assert reservation_args[1] == 'createReservation'
    assert Pairing.objects.count() == 1

    # Changing price code update pairing and planitec user
    new_params = params.copy()
    new_params['price_code'] = 'T2'
    new_side_effect = side_effect[:]
    new_side_effect[0] = {
        'modificationStatus': 'OK',
        'externalUserIdentifier': '22b9c0d91fdc4f379d1356a4aaa9d38b',
        'requestDate': datetime(2019, 1, 9, 15, 41),
        'requestName': 'updatePerson',
        'responseDate': datetime(2019, 1, 9, 15, 41),
    }
    mock_call_planitech = mock_planitech(monkeypatch, side_effect=new_side_effect)
    response = app.post_json('/planitech/slug-planitech/createreservation', params=new_params)
    json_resp = response.json
    assert json_resp['err'] == 0
    assert json_resp['data']['reservation_id'] == 1

    person_args = mock_call_planitech.call_args_list[0][0]
    assert person_args[1] == 'updatePerson'
    person_args = person_args[2]
    assert person_args['pricingCode'] == 'T2'
    external_id = person_args['externalUserIdentifier']
    assert Pairing.objects.count() == 1
    pairing = Pairing.objects.get(external_id=external_id)
    assert pairing.price_code == 'T2'

    reservation_args = mock_call_planitech.call_args_list[1][0]
    assert reservation_args[1] == 'createReservation'

    # Create reservation failed
    mock_call_planitech = mock_planitech(
        monkeypatch, return_value={'creationStatus': 'NOTOK', 'reservationIdentifier': 1}
    )
    response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == 'Reservation creation failed: NOTOK'

    # Create reservation failed - nor reservation ID
    mock_call_planitech = mock_planitech(monkeypatch, return_value={'creationStatus': 'OK'})
    response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == 'Reservation creation failed: no reservation ID'


def test_get_reservation_price(app, connector, monkeypatch):
    side_effect = [
        {
            'creationStatus': 'OK',
            'externalUserIdentifier': '22b9c0d91fdc4f379d1356a4aaa9d38b',
            'requestDate': datetime(2019, 1, 9, 15, 41),
            'requestName': 'createPerson',
            'responseDate': datetime(2019, 1, 9, 15, 41),
            'userIdentifier': 13826.0,
        },
        {'calculationStatus': 'OK', 'calculatedPrice': 10},
    ]
    mock_call_planitech = mock_planitech(monkeypatch, side_effect=side_effect)
    params = {
        'date': '2018-11-11',
        'start_time': '10:00',
        'end_time': '11:00',
        'place_id': 1,
        'name_id': 'john-doe',
        'first_name': 'jon',
        'last_name': 'doe',
        'email': 'jon.doe@localhost',
        'activity_id': 1,
        'type_id': 1,
    }

    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
    json_resp = response.json
    assert json_resp['err'] == 0
    assert json_resp['data']['price'] == 10

    person_args = mock_call_planitech.call_args_list[0][0]
    assert person_args[1] == 'createPerson'
    person_args = person_args[2]
    assert person_args['mail'] == 'jon.doe@localhost'
    assert person_args['firstName'] == 'jon'
    assert person_args['name'] == 'doe'
    assert person_args['pricingCode'] == 'T1'
    external_id = person_args['externalUserIdentifier']
    pairing = Pairing.objects.get(resource=connector, name_id='john-doe')
    assert pairing.external_id == external_id
    assert pairing.price_code == 'T1'

    reservation_args = mock_call_planitech.call_args_list[1][0]
    assert reservation_args[1] == 'getFutureReservationPrice'
    reservation_args = reservation_args[2]
    assert reservation_args['start'] == datetime(2018, 11, 11, 10, 0)
    assert reservation_args['end'] == datetime(2018, 11, 11, 11, 0)
    assert reservation_args['places'] == [1]
    assert reservation_args['contractorExternalIdentifier'] == pairing.external_id
    assert reservation_args['activityID'] == 1
    assert reservation_args['typeID'] == 1

    # Second call for same user : no planitech person created
    mock_call_planitech = mock_planitech(monkeypatch, return_value=side_effect[1])
    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
    json_resp = response.json
    assert json_resp['err'] == 0
    assert json_resp['data']['price'] == 10

    reservation_args = mock_call_planitech.call_args_list[0][0]
    assert reservation_args[1] == 'getFutureReservationPrice'
    assert Pairing.objects.count() == 1

    # Failure
    mock_call_planitech = mock_planitech(
        monkeypatch, return_value={'calculationStatus': 'NOTOK', 'calculatedPrice': 10}
    )
    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == 'Get reservation price failed: NOTOK'

    # Create reservation failed - nor reservation ID
    mock_call_planitech = mock_planitech(monkeypatch, return_value={'calculationStatus': 'OK'})
    response = app.post_json('/planitech/slug-planitech/getreservationprice', params=params)
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == 'Get reservation price failed: no price'


def test_getplaces_referential(app, connector, monkeypatch):
    side_effect = [
        {
            'placesList': [
                {'identifier': 1.0, 'label': 'salle 1'},
                {'identifier': 2.0, 'label': 'salle 2'},
                {'identifier': 3.0, 'label': 'salle 3'},
                {'identifier': 4.0, 'label': 'salle 4'},
            ]
        },
        {
            'requestedPlaces': [
                {
                    'identifier': 1.0,
                    'capacity': 10.0,
                    'streetNumber': 1,
                    'address1': 'rue planitech',
                    'city': 'thecity',
                    'zipCode': '00000',
                },
                {'identifier': 2.0, 'capacity': 20.0, 'some_custom_field': 'Yes'},
                {'identifier': 3.0, 'capacity': 30.0, 'some_custom_field': 'Yes'},
                {'identifier': 4.0, 'capacity': None},
            ]
        },
    ]
    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplacesreferential')
    expected_res = {
        '4': {
            'capacity': None,
            'label': 'salle 4',
            'identifier': 4,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': None,
        },
        '3': {
            'capacity': 30,
            'label': 'salle 3',
            'identifier': 3,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': 'Yes',
        },
        '2': {
            'capacity': 20,
            'label': 'salle 2',
            'identifier': 2,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': 'Yes',
        },
        '1': {
            'capacity': 10,
            'label': 'salle 1',
            'identifier': 1,
            'street_number': 1,
            'address': 'rue planitech',
            'city': 'thecity',
            'zipcode': '00000',
            'some_custom_field': None,
        },
    }
    assert response.json['data'] == expected_res

    # Filter on custom fields
    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplacesreferential?some_custom_field=Yes')
    assert response.json['data'] == {
        '3': {
            'capacity': 30,
            'label': 'salle 3',
            'identifier': 3,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': 'Yes',
        },
        '2': {
            'capacity': 20,
            'label': 'salle 2',
            'identifier': 2,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': 'Yes',
        },
    }

    # Filter on custom fields and capacity
    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplacesreferential?some_custom_field=Yes&min_capacity=25')
    assert response.json['data'] == {
        '3': {
            'capacity': 30,
            'label': 'salle 3',
            'identifier': 3,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': 'Yes',
        }
    }

    # Unkown filter filters nothing
    response = app.get('/planitech/slug-planitech/getplacesreferential?unkownfilter=foo')
    expected_res = {
        '4': {
            'capacity': None,
            'label': 'salle 4',
            'identifier': 4,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': None,
        },
        '3': {
            'capacity': 30,
            'label': 'salle 3',
            'identifier': 3,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': 'Yes',
        },
        '2': {
            'capacity': 20,
            'label': 'salle 2',
            'identifier': 2,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
            'some_custom_field': 'Yes',
        },
        '1': {
            'capacity': 10,
            'label': 'salle 1',
            'identifier': 1,
            'street_number': 1,
            'address': 'rue planitech',
            'city': 'thecity',
            'zipcode': '00000',
            'some_custom_field': None,
        },
    }
    assert response.json['data'] == expected_res


def test_getplace(app, connector, monkeypatch):
    side_effect = [
        {'placesList': [{'identifier': 1.0, 'label': 'salle 1'}, {'identifier': 2.0, 'label': 'salle 2'}]},
        {
            'requestedPlaces': [
                {
                    'identifier': 1.0,
                    'capacity': 10.0,
                    'streetNumber': 1,
                    'address1': 'rue planitech',
                    'city': 'thecity',
                    'zipCode': '00000',
                },
                {'identifier': 2.0, 'capacity': 20.0, 'some_custom_field': 'Yes'},
            ]
        },
    ]

    # filter by identifier
    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplace?id=2')
    assert response.json['data'] == {
        'capacity': 20,
        'label': 'salle 2',
        'identifier': 2,
        'street_number': None,
        'address': None,
        'city': None,
        'zipcode': None,
        'some_custom_field': 'Yes',
    }

    # bad value
    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplace?id=toto')
    assert response.json['err'] == 1
    assert response.json['err_desc'] == 'ID must be an integer'

    # missing place
    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplace?id=12')
    assert response.json['err'] == 1
    assert response.json['err_desc'] == 'No place with ID 12'


def test_getplaces_referential_no_configuration(app, connector, monkeypatch):
    # Custom fields not returned if no configuration
    connector.custom_fields = None
    connector.save()
    side_effect = [
        {'placesList': [{'identifier': 1.0, 'label': 'salle 1'}, {'identifier': 2.0, 'label': 'salle 2'}]},
        {
            'requestedPlaces': [
                {
                    'identifier': 1.0,
                    'capacity': 10.0,
                    'streetNumber': 1,
                    'address1': 'rue planitech',
                    'city': 'thecity',
                    'zipCode': '00000',
                },
                {'identifier': 2.0, 'capacity': 20.0, 'some_custom_field': 'Yes'},
            ]
        },
    ]

    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplacesreferential')
    mock_planitech(monkeypatch, side_effect=side_effect)
    response = app.get('/planitech/slug-planitech/getplacesreferential')
    expected_res = {
        '2': {
            'capacity': 20,
            'label': 'salle 2',
            'identifier': 2,
            'street_number': None,
            'address': None,
            'city': None,
            'zipcode': None,
        },
        '1': {
            'capacity': 10,
            'label': 'salle 1',
            'identifier': 1,
            'street_number': 1,
            'address': 'rue planitech',
            'city': 'thecity',
            'zipcode': '00000',
        },
    }
    assert response.json['data'] == expected_res


def test_getplaces_referential_use_cache(app, connector):
    cache_key = 'planitech-%s-places' % connector.id
    cache.set(cache_key, {'some': {'data': 'foo'}})
    response = app.get('/planitech/slug-planitech/getplacesreferential')
    assert response.json_body['data'] == {'some': {'data': 'foo'}}
    cache.delete(cache_key)


def test_login(connector):
    @urlmatch(netloc=r'(.*\.)?planitech\.com$')
    def planitech_mock(url, request):
        raise requests.exceptions.RequestException("Bad news")

    with HTTMock(planitech_mock):
        with pytest.raises(APIError) as excinfo:
            connector._login()
        assert str(excinfo.value) == 'Authentication to Planitec failed: Bad news'


def test_update_reservation(app, connector, monkeypatch):
    mock_call_planitech = mock_planitech(monkeypatch, return_value={'modificationStatus': 'OK'})
    response = app.post_json(
        '/planitech/slug-planitech/updatereservation', params={'status': 'confirmed', 'reservation_id': 1}
    )
    json_resp = response.json
    assert json_resp['err'] == 0
    assert json_resp['data']['raw_data'] == {'modificationStatus': 'OK'}
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['reservationIdentifier'] == 1
    assert call_params['situation'] == 3

    # Update failed
    mock_planitech(monkeypatch, return_value={'modificationStatus': 'NOTOK'})
    response = app.post_json(
        '/planitech/slug-planitech/updatereservation', params={'status': 'confirmed', 'reservation_id': 1}
    )
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == 'Update reservation failed: NOTOK'

    # Connector bad param
    response = app.post_json(
        '/planitech/slug-planitech/updatereservation', params={'status': 'confirmed'}, status=400
    )
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == "'reservation_id' is a required property"


def test_get_reservations_infos(app, connector, monkeypatch):
    mock_call_planitech = mock_planitech(
        monkeypatch,
        return_value={
            'requestedReservations': [
                {
                    'reservation': 'data',
                    'activity': {'reservations': 'activity_data'},
                    'contractor': {'reservations': 'contractor_data'},
                }
            ]
        },
    )
    response = app.get('/planitech/slug-planitech/getreservationsinfo/?reservation_id=1')
    json_resp = response.json
    assert json_resp['err'] == 0
    assert json_resp['data'] == {
        'reservation': 'data',
        'activity': {},
        'contractor': {},
    }
    assert mock_call_planitech.call_args[0][1] == 'getReservationsInfo'
    assert mock_call_planitech.call_args[0][2] == {'reservationIdentifiers': [1.0]}


def freegaps_data():
    referential = collections.OrderedDict(
        [
            (1.0, {'capacity': 10.0, 'label': 'salle 1', 'identifier': 1.0}),
            (
                2.0,
                {'capacity': 20.0, 'label': 'salle 2', 'identifier': 2.0, 'some_custom_field': 'Yes'},
            ),
            (
                3.0,
                {'capacity': 30.0, 'label': 'salle 3', 'identifier': 3.0, 'some_custom_field': 'Yes'},
            ),
        ]
    )
    place1_gaps = [
        [
            datetime(year=2018, month=11, day=11, hour=10, minute=0),
            datetime(year=2018, month=11, day=11, hour=11, minute=0),
        ],
        [
            datetime(year=2018, month=11, day=12, hour=10, minute=0),
            datetime(year=2018, month=11, day=12, hour=11, minute=0),
        ],
    ]
    place2_gaps = [
        [
            datetime(year=2018, month=11, day=11, hour=10, minute=0),
            datetime(year=2018, month=11, day=11, hour=11, minute=0),
        ],
        [
            datetime(year=2018, month=11, day=12, hour=10, minute=0),
            datetime(year=2018, month=11, day=12, hour=11, minute=0),
        ],
        [
            datetime(year=2018, month=11, day=13, hour=10, minute=0),
            datetime(year=2018, month=11, day=13, hour=11, minute=0),
        ],
    ]
    planitech_r = {
        'availablePlaces': [
            {'placeIdentifier': 1.0, 'freeGaps': place1_gaps},
            {'placeIdentifier': 2.0, 'freeGaps': place2_gaps},
        ]
    }
    return referential, planitech_r


def test_get_freegaps(app, connector, monkeypatch, settings):
    settings.LANGUAGE_CODE = 'fr-fr'
    referential, planitech_r = freegaps_data()
    mock_call_planitech = mock_planitech(monkeypatch, return_value=planitech_r, referential=referential)

    # date display
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
        '&start_date=2018-11-11&display=date'
    )
    assert response.json['data'] == [
        {'id': '2018-11-11', 'text': 'dimanche 11 novembre 2018', 'short_text': '11/11/2018'},
        {'id': '2018-11-12', 'text': 'lundi 12 novembre 2018', 'short_text': '12/11/2018'},
        {'id': '2018-11-13', 'text': 'mardi 13 novembre 2018', 'short_text': '13/11/2018'},
    ]

    # place display
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
        '&start_date=2018-11-11&display=place'
    )
    assert response.json['data'] == [{'id': 1.0, 'text': 'salle 1'}, {'id': 2.0, 'text': 'salle 2'}]

    # full display
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
        '&start_date=2018-11-11&display=full'
    )
    res = response.json['data']
    assert 'place' in res
    assert 'date' in res
    full = res['full']
    assert len(full) == 3

    place_1 = full[0]
    assert place_1['id'] == 1.0
    assert place_1['text'] == 'salle 1'
    assert place_1['dates'] == [
        {'available': True, 'id': '2018-11-11'},
        {'available': True, 'id': '2018-11-12'},
        {'available': False, 'id': '2018-11-13'},
    ]

    place_2 = full[1]
    assert place_2['id'] == 2.0
    assert place_2['text'] == 'salle 2'
    assert place_2['dates'] == [
        {'available': True, 'id': '2018-11-11'},
        {'available': True, 'id': '2018-11-12'},
        {'available': True, 'id': '2018-11-13'},
    ]

    place_3 = full[2]
    assert place_3['id'] == 3.0
    assert place_3['text'] == 'salle 3'
    assert place_3['dates'] == [
        {'available': False, 'id': '2018-11-11'},
        {'available': False, 'id': '2018-11-12'},
        {'available': False, 'id': '2018-11-13'},
    ]

    # general params interpretation
    mock_call_planitech.reset_mock()
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
        '&start_date=2018-11-11&end_date=2018-11-12&display=date'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['startingDate'] == datetime(2018, 11, 11, 10, 0)
    assert call_params['endingDate'] == datetime(2018, 11, 12, 00, 0)
    assert call_params['placeIdentifiers'] == [1.0, 2.0, 3.0]
    assert call_params['requestedStartingTime'] == 0.0  # means startingDate
    assert call_params['requestedEndingTime'] == 60.0  # means startingDate + 60 minutes
    assert 'reservationDays' not in call_params

    # no end date means only the starting date is interesting
    # so ending date is the day after at midnight
    # which gives only results for starting date
    mock_call_planitech.reset_mock()
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
        '&start_date=2018-11-11&display=place'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['startingDate'] == datetime(2018, 11, 11, 10, 0)
    assert call_params['endingDate'] == datetime(2018, 11, 12, 0, 0)

    # capacity used against referential to restrict placeIdentifiers
    mock_call_planitech.reset_mock()
    app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
        '&start_date=2018-11-11&max_capacity=15&display=place'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['placeIdentifiers'] == [1.0]

    # place_id disable filtering
    mock_call_planitech.reset_mock()
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
        '&start_date=2018-11-11&max_capacity=15&place_id=2&display=place'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['placeIdentifiers'] == [2.0]

    # weekdays
    mock_call_planitech.reset_mock()
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
        '&start_date=2018-11-11&max_capacity=15&place_id=2&display=place'
        '&weekdays=0,1,2'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['reservationDays'] == [0, 1, 2]

    # custom field restriction
    mock_call_planitech.reset_mock()
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
        '&start_date=2018-11-11&some_custom_field=Yes&display=place'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['placeIdentifiers'] == [2.0, 3.0]

    # custom field and capacity restriction
    mock_call_planitech.reset_mock()
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
        '&start_date=2018-11-11&some_custom_field=Yes&min_capacity=25&display=place'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['placeIdentifiers'] == [3.0]

    # BAD REQUEST
    # bad date format
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
        '&start_date=notadate&display=place',
        status=400,
    )
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == 'invalid value for parameter "start_date (YYYY-MM-DD expected)"'

    # bad time format
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=notatime&&end_time=14:00'
        '&start_date=2018-11-11&display=place'
    )
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == "Invalid time format: notatime"

    # start_date or start_days required
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=notatime&&end_time=14:00&display=place'
    )
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == "start_date or start_days is required"

    # invalid display param
    response = app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=notatime&&end_time=14:00'
        '&start_date=2018-11-11&display=unkown'
    )
    json_resp = response.json
    assert json_resp['err'] == 1
    assert json_resp['err_desc'] == "Valid display are: date, place, full"


def test_get_freegaps_start_days(app, connector, monkeypatch, settings, freezer):
    freezer.move_to('2018-11-09 23:50:00')
    settings.LANGUAGE_CODE = 'fr-fr'
    referential, planitech_r = freegaps_data()
    mock_call_planitech = mock_planitech(monkeypatch, return_value=planitech_r, referential=referential)

    # starting_date and ending_date can be specified as days delta from now
    app.get(
        '/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
        '&start_days=2&end_days=4&display=place'
    )
    call_params = mock_call_planitech.call_args[0][2]
    assert call_params['startingDate'] == datetime(2018, 11, 11, 10, 0)
    assert call_params['endingDate'] == datetime(2018, 11, 13, 0, 0)


def test_get_extensions(app, connector):

    post_data = {
        'extension_0_name': 'foo',
        'extension_0_value': 'foo_value',
        'extension_1_name': 'bar',
        'extension_1_value': 'bar_value',
    }
    extensions = get_extensions(post_data)
    assert len(extensions) == 2
    for extension in extensions:
        assert extension['name'] in ('foo', 'bar')
        assert extension['value'] == extension['name'] + '_value'
        assert extension['type'] == 'string'

    post_data = {
        'extension_aa': 'foo',
    }
    with pytest.raises(APIError) as excinfo:
        extensions = get_extensions(post_data)
    assert str(excinfo.value) == "Wrong extension format for : 'extension_aa'"

    post_data = {
        'extension_0_aa': 'foo',
    }
    with pytest.raises(APIError) as excinfo:
        extensions = get_extensions(post_data)
    assert str(excinfo.value) == "Missing 'name' in extension"
