from datetime import datetime

import pytest
from django.test import override_settings
from django.urls import reverse
from django.utils.timezone import utc
from rest_framework.status import (
    HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN)

from ..utils import (
    ALL_METHODS, check_list_endpoint_base_fields, check_method_status_codes,
    check_response_objects, get, get_ids_from_results)

list_url = reverse('enforcement:v1:valid_parking-list')


def list_url_for(reg_num):
    return '{url}?reg_num={reg_num}'.format(url=list_url, reg_num=reg_num)


list_url_for_abc = list_url_for('ABC-123')


def get_url(kind, parking):
    if kind == 'list':
        return list_url
    elif kind == 'list_by_reg_num':
        return list_url_for(parking.registration_number)
    else:
        assert kind == 'detail'
        return reverse('enforcement:v1:valid_parking-detail',
                       kwargs={'pk': parking.pk})


def get_parking_object(parking_type, parking, disc_parking):
    if parking_type == 'paid':
        return parking
    else:
        assert parking_type == 'disc_parking'
        return disc_parking


ALL_URL_KINDS = ['list', 'list_by_reg_num', 'detail']
ALL_PARKING_KINDS = ['paid', 'disc_parking']


@pytest.mark.parametrize('url_kind', ALL_URL_KINDS)
def test_permission_checks(api_client, operator_api_client, parking, url_kind):
    url = get_url(url_kind, parking)
    check_method_status_codes(
        api_client, [url], ALL_METHODS, HTTP_401_UNAUTHORIZED)
    check_method_status_codes(
        operator_api_client, [url], ALL_METHODS, HTTP_403_FORBIDDEN,
        error_code='permission_denied')


@pytest.mark.parametrize('url_kind', ALL_URL_KINDS)
def test_disallowed_methods(staff_api_client, parking, url_kind):
    url = get_url(url_kind, parking)
    disallowed_methods = ('post', 'put', 'patch', 'delete')
    check_method_status_codes(
        staff_api_client, [url], disallowed_methods, 405)


def test_reg_num_is_required(staff_api_client, parking):
    response = staff_api_client.get(list_url)
    assert response.status_code == HTTP_400_BAD_REQUEST
    assert response.data == {'reg_num': ['This field is required.']}


def test_list_endpoint_base_fields(staff_api_client):
    parking_data = get(staff_api_client, list_url_for_abc)
    check_list_endpoint_base_fields(parking_data)


@pytest.mark.parametrize('parking_type', ALL_PARKING_KINDS)
def test_list_endpoint_data(staff_api_client, parking_type, parking, disc_parking):
    parking_object = get_parking_object(parking_type, parking, disc_parking)
    data = get(staff_api_client, get_url('list_by_reg_num', parking_object))
    assert len(data['results']) == 1
    parking_data = data['results'][0]
    check_parking_data_keys(parking_data)
    check_parking_data_matches_parking_object(data['results'][0], parking_object)


def check_parking_data_keys(parking_data):
    parking_data_keys = {
        'id', 'created_at', 'modified_at',
        'registration_number',
        'time_start', 'time_end', 'zone',
        'operator', 'operator_name',
    }

    if parking_data.get('is_disc_parking'):
        parking_data_keys.add('is_disc_parking')

    assert set(parking_data.keys()) == parking_data_keys


def check_parking_data_matches_parking_object(parking_data, parking_obj):
    """
    Check that a parking data dict and an actual Parking object match.
    """

    # string and integer valued fields should match 1:1
    for field in {'registration_number', 'zone'}:
        assert parking_data[field] == getattr(parking_obj, field)

    assert parking_data['id'] == str(parking_obj.id)  # UUID -> str
    assert parking_data['operator'] == str(parking_obj.operator.id)
    assert parking_data['created_at'] == iso8601_us(parking_obj.created_at)
    assert parking_data['modified_at'] == iso8601_us(parking_obj.modified_at)
    assert parking_data['time_start'] == iso8601(parking_obj.time_start)
    assert parking_data['time_end'] == iso8601(parking_obj.time_end)
    assert parking_data['operator_name'] == str(parking_obj.operator.name)
    assert parking_data.get('is_disc_parking', False) == parking_obj.is_disc_parking


def iso8601(dt):
    return dt.strftime('%Y-%m-%dT%H:%M:%SZ')


def iso8601_us(dt):
    return dt.strftime('%Y-%m-%dT%H:%M:%S.%fZ')


def test_registration_number_filter(operator, staff_api_client, parking_factory):
    p1 = parking_factory(registration_number='ABC-123', operator=operator)
    p2 = parking_factory(registration_number='ZYX-987', operator=operator)
    p3 = parking_factory(registration_number='ZYX-987', operator=operator)
    p4 = parking_factory(registration_number='Zyx987 ', operator=operator)

    results = get(staff_api_client, list_url_for('ABC-123'))['results']
    assert get_ids_from_results(results) == {p1.id}

    results = get(staff_api_client, list_url_for('ZYX-987'))['results']
    assert get_ids_from_results(results) == {p2.id, p3.id, p4.id}

    results = get(staff_api_client, list_url_for('zyx987'))['results']
    assert get_ids_from_results(results) == {p2.id, p3.id, p4.id}

    results = get(staff_api_client, list_url_for('LOL-777'))['results']
    assert not results


@pytest.mark.parametrize('name', [
    'before_all',
    'at_start_of_1st',
    'after_start_of_1st',
    'before_end_of_1st',
    'between_1st_and_2nd',
    'after_start_of_2nd',
    'before_end_of_2nd',
    'at_end_of_2nd',
    'less_than_15min_after_2nd',
    'more_than_15min_after_2nd',
    'less_than_day_after_2nd',
    'more_than_day_after_2nd',
    'now',
])
def test_time_filtering(operator, staff_api_client, parking_factory, name):
    p1 = parking_factory(
        registration_number='ABC-123',
        time_start=datetime(2012, 1, 1, 12, 0, 0, tzinfo=utc),
        time_end=datetime(2014, 1, 1, 12, 0, 0, tzinfo=utc),
        operator=operator)
    p2 = parking_factory(
        registration_number='ABC-123',
        time_start=datetime(2014, 1, 1, 12, 0, 0, tzinfo=utc),
        time_end=datetime(2016, 1, 1, 12, 0, 0, tzinfo=utc),
        operator=operator)
    p3 = parking_factory(registration_number='ABC-123')

    (time, expected_parkings) = {
        'before_all': ('2000-01-01T12:00:00Z', []),
        'at_start_of_1st': ('2012-01-01T12:00:00Z', [p1]),
        'after_start_of_1st': ('2012-01-01T12:00:01Z', [p1]),
        'before_end_of_1st': ('2014-01-01T11:59:59Z', [p1]),
        'between_1st_and_2nd': ('2014-01-01T12:00:00Z', [p1, p2]),
        'after_start_of_2nd': ('2014-01-01T12:00:01Z', [p2]),
        'before_end_of_2nd': ('2016-01-01T11:59:59Z', [p2]),
        'at_end_of_2nd': ('2016-01-01T12:00:00Z', [p2]),
        'less_than_15min_after_2nd': ('2016-01-01T12:14:59Z', [p2]),
        'more_than_15min_after_2nd': ('2016-01-02T12:15:01Z', []),
        'less_than_day_after_2nd': ('2016-01-01T22:00:00Z', []),
        'more_than_day_after_2nd': ('2016-01-02T13:00:00Z', []),
        'now': ('', [p3]),
    }[name]

    filtering = '&time={}'.format(time) if time else ''
    response = get(staff_api_client, list_url_for_abc + filtering)
    check_response_objects(response, expected_parkings)


@pytest.mark.parametrize('parking_type', ALL_PARKING_KINDS)
@override_settings(PARKKIHUBI_NONE_END_TIME_REPLACEMENT='2030-12-31T23:59:59Z')
def test_null_time_end_is_replaced_correctly(parking_type, staff_api_client, parking, disc_parking):
    parking_object = get_parking_object(parking_type, parking, disc_parking)
    parking_object.time_end = None
    parking_object.save()
    data = get(staff_api_client, get_url('list_by_reg_num', parking_object))
    parking_data = data['results'][0]
    check_parking_data_keys(parking_data)

    assert parking_data['time_end'] == '2030-12-31T23:59:59Z'

from datetime import datetime

import pytest
from django.test import override_settings
from django.urls import reverse
from django.utils.timezone import utc
from rest_framework.status import (
    HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN)

from ..utils import (
    ALL_METHODS, check_list_endpoint_base_fields, check_method_status_codes,
    check_response_objects, get, get_ids_from_results)

list_url = reverse('enforcement:v1:valid_parking-list')


def list_url_for(reg_num):
    return '{url}?reg_num={reg_num}'.format(url=list_url, reg_num=reg_num)


list_url_for_abc = list_url_for('ABC-123')


def get_url(kind, parking):
    if kind == 'list':
        return list_url
    elif kind == 'list_by_reg_num':
        return list_url_for(parking.registration_number)
    else:
        assert kind == 'detail'
        return reverse('enforcement:v1:valid_parking-detail',
                       kwargs={'pk': parking.pk})


def get_parking_object(parking_type, parking, disc_parking):
    if parking_type == 'paid':
        return parking
    else:
        assert parking_type == 'disc_parking'
        return disc_parking


ALL_URL_KINDS = ['list', 'list_by_reg_num', 'detail']
ALL_PARKING_KINDS = ['paid', 'disc_parking']


@pytest.mark.parametrize('url_kind', ALL_URL_KINDS)
def test_permission_checks(api_client, operator_api_client, parking, url_kind):
    url = get_url(url_kind, parking)
    check_method_status_codes(
        api_client, [url], ALL_METHODS, HTTP_401_UNAUTHORIZED)
    check_method_status_codes(
        operator_api_client, [url], ALL_METHODS, HTTP_403_FORBIDDEN,
        error_code='permission_denied')


@pytest.mark.parametrize('url_kind', ALL_URL_KINDS)
def test_disallowed_methods(staff_api_client, parking, url_kind):
    url = get_url(url_kind, parking)
    disallowed_methods = ('post', 'put', 'patch', 'delete')
    check_method_status_codes(
        staff_api_client, [url], disallowed_methods, 405)


def test_reg_num_is_required(staff_api_client, parking):
    response = staff_api_client.get(list_url)
    assert response.status_code == HTTP_400_BAD_REQUEST
    assert response.data == {'reg_num': ['This field is required.']}


def test_list_endpoint_base_fields(staff_api_client):
    parking_data = get(staff_api_client, list_url_for_abc)
    check_list_endpoint_base_fields(parking_data)


@pytest.mark.parametrize('parking_type', ALL_PARKING_KINDS)
def test_list_endpoint_data(staff_api_client, parking_type, parking, disc_parking):
    parking_object = get_parking_object(parking_type, parking, disc_parking)
    data = get(staff_api_client, get_url('list_by_reg_num', parking_object))
    assert len(data['results']) == 1
    parking_data = data['results'][0]
    check_parking_data_keys(parking_data)
    check_parking_data_matches_parking_object(data['results'][0], parking_object)


def check_parking_data_keys(parking_data):
    parking_data_keys = {
        'id', 'created_at', 'modified_at',
        'registration_number',
        'time_start', 'time_end', 'zone',
        'operator', 'operator_name',
    }

    if parking_data.get('is_disc_parking'):
        parking_data_keys.add('is_disc_parking')

    assert set(parking_data.keys()) == parking_data_keys


def check_parking_data_matches_parking_object(parking_data, parking_obj):
    """
    Check that a parking data dict and an actual Parking object match.
    """

    # string and integer valued fields should match 1:1
    for field in {'registration_number', 'zone'}:
        assert parking_data[field] == getattr(parking_obj, field)

    assert parking_data['id'] == str(parking_obj.id)  # UUID -> str
    assert parking_data['operator'] == str(parking_obj.operator.id)
    assert parking_data['created_at'] == iso8601_us(parking_obj.created_at)
    assert parking_data['modified_at'] == iso8601_us(parking_obj.modified_at)
    assert parking_data['time_start'] == iso8601(parking_obj.time_start)
    assert parking_data['time_end'] == iso8601(parking_obj.time_end)
    assert parking_data['operator_name'] == str(parking_obj.operator.name)
    assert parking_data.get('is_disc_parking', False) == parking_obj.is_disc_parking


def iso8601(dt):
    return dt.strftime('%Y-%m-%dT%H:%M:%SZ')


def iso8601_us(dt):
    return dt.strftime('%Y-%m-%dT%H:%M:%S.%fZ')


def test_registration_number_filter(operator, staff_api_client, parking_factory):
    p1 = parking_factory(registration_number='ABC-123', operator=operator)
    p2 = parking_factory(registration_number='ZYX-987', operator=operator)
    p3 = parking_factory(registration_number='ZYX-987', operator=operator)
    p4 = parking_factory(registration_number='Zyx987 ', operator=operator)

    results = get(staff_api_client, list_url_for('ABC-123'))['results']
    assert get_ids_from_results(results) == {p1.id}

    results = get(staff_api_client, list_url_for('ZYX-987'))['results']
    assert get_ids_from_results(results) == {p2.id, p3.id, p4.id}

    results = get(staff_api_client, list_url_for('zyx987'))['results']
    assert get_ids_from_results(results) == {p2.id, p3.id, p4.id}

    results = get(staff_api_client, list_url_for('LOL-777'))['results']
    assert not results


@pytest.mark.parametrize('name', [
    'before_all',
    'at_start_of_1st',
    'after_start_of_1st',
    'before_end_of_1st',
    'between_1st_and_2nd',
    'after_start_of_2nd',
    'before_end_of_2nd',
    'at_end_of_2nd',
    'less_than_15min_after_2nd',
    'more_than_15min_after_2nd',
    'less_than_day_after_2nd',
    'more_than_day_after_2nd',
    'now',
])
def test_time_filtering(operator, staff_api_client, parking_factory, name):
    p1 = parking_factory(
        registration_number='ABC-123',
        time_start=datetime(2012, 1, 1, 12, 0, 0, tzinfo=utc),
        time_end=datetime(2014, 1, 1, 12, 0, 0, tzinfo=utc),
        operator=operator)
    p2 = parking_factory(
        registration_number='ABC-123',
        time_start=datetime(2014, 1, 1, 12, 0, 0, tzinfo=utc),
        time_end=datetime(2016, 1, 1, 12, 0, 0, tzinfo=utc),
        operator=operator)
    p3 = parking_factory(registration_number='ABC-123')

    (time, expected_parkings) = {
        'before_all': ('2000-01-01T12:00:00Z', []),
        'at_start_of_1st': ('2012-01-01T12:00:00Z', [p1]),
        'after_start_of_1st': ('2012-01-01T12:00:01Z', [p1]),
        'before_end_of_1st': ('2014-01-01T11:59:59Z', [p1]),
        'between_1st_and_2nd': ('2014-01-01T12:00:00Z', [p1, p2]),
        'after_start_of_2nd': ('2014-01-01T12:00:01Z', [p2]),
        'before_end_of_2nd': ('2016-01-01T11:59:59Z', [p2]),
        'at_end_of_2nd': ('2016-01-01T12:00:00Z', [p2]),
        'less_than_15min_after_2nd': ('2016-01-01T12:14:59Z', [p2]),
        'more_than_15min_after_2nd': ('2016-01-02T12:15:01Z', []),
        'less_than_day_after_2nd': ('2016-01-01T22:00:00Z', []),
        'more_than_day_after_2nd': ('2016-01-02T13:00:00Z', []),
        'now': ('', [p3]),
    }[name]

    filtering = '&time={}'.format(time) if time else ''
    response = get(staff_api_client, list_url_for_abc + filtering)
    check_response_objects(response, expected_parkings)


@pytest.mark.parametrize('parking_type', ALL_PARKING_KINDS)
@override_settings(PARKKIHUBI_NONE_END_TIME_REPLACEMENT='2030-12-31T23:59:59Z')
def test_null_time_end_is_replaced_correctly(parking_type, staff_api_client, parking, disc_parking):
    parking_object = get_parking_object(parking_type, parking, disc_parking)
    parking_object.time_end = None
    parking_object.save()
    data = get(staff_api_client, get_url('list_by_reg_num', parking_object))
    parking_data = data['results'][0]
    check_parking_data_keys(parking_data)

    assert parking_data['time_end'] == '2030-12-31T23:59:59Z'

Check that a parking data dict and an actual Parking object match. # string and integer valued fields should match 1:1 # UUID -> str