from typing import Callable

import pytest
from django.test import Client
from django.urls import reverse
from hypothesis import given
from hypothesis import strategies as st
from structlog.testing import capture_logs

from .models import Teil

names = st.text(alphabet=st.characters(exclude_categories=["C"]), min_size=1)


@given(data=names)
def test_submitted_data_ends_up_in_database(data, session: Client):
    with pytest.raises(Teil.DoesNotExist):
        Teil.objects.get(name=data)

    response = session.post(reverse("collector:enter"), data={"new_name": data})
    assert response.status_code == 302
    assert Teil.objects.get(name=data)


@given(data=names)
def test_entering_same_name_twice_does_not_change_database_entry(data, session: Client):
    assert not Teil.objects.filter(name=data).exists()

    response = session.post(reverse("collector:enter"), data={"new_name": data})
    assert response.status_code == 302
    assert Teil.objects.filter(name=data).count() == 1

    response = session.post(reverse("collector:enter"), data={"new_name": data})
    assert response.status_code == 302
    assert Teil.objects.filter(name=data).count() == 1


@pytest.mark.parametrize(
    "http_method,expected_status",
    [
        ("GET", 405),
        ("PATCH", 405),
        ("POST", 302),
        ("PUT", 405),
    ],
)
def test_enter_endpoint_accepts_only_post_requests(
    client: Client,
    http_method: str,
    expected_status: int,
    random_name: Callable[[int], str],
):
    client_method = getattr(client, http_method.lower())

    response = client_method(
        reverse("collector:enter"), data={"new_name": random_name(8)}
    )
    assert response.status_code == expected_status


def test_enter_endpoints_emits_expected_logs(
    client: Client, random_name: Callable[[int], str]
):
    """
    note: capture_logs() yields a list of dicts, not a list of logfmt lines
    that we configured for the app
    """
    same_name = random_name(10)

    with capture_logs() as logs:
        response = client.post(reverse("collector:enter"), data={"new_name": same_name})
        assert response.status_code == 302
        assert any(
            (
                le["event"].lower() == "New Teil entered".lower()
                and le["log_level"].lower() == "info"
                and "teil_id" in le
                for le in logs
            )
        )

    with capture_logs() as logs:
        response = client.post(reverse("collector:enter"), data={"new_name": same_name})
        assert response.status_code == 302
        assert any(
            (
                le["event"].lower() == "Teil already existed".lower()
                and le["log_level"].lower() == "warning"
                and "teil_id" not in le
                for le in logs
            )
        )


def test_model_manager_lists_newest_teil_first():
    t1 = Teil.objects.create(name="Teil 1")
    t2 = Teil.objects.create(name="Teil 2")
    t3 = Teil.objects.create(name="Teil 3")

    assert t1.modified < t2.modified < t3.modified

    t = Teil.objects.all()

    assert t[2].modified < t[1].modified < t[0].modified