import asyncio
import pytest
from datetime import datetime
from systemd import journal

from tests.test_graphql.test_websocket import init_graphql


def assert_log_entry_equals_to_journal_entry(api_entry, journal_entry):
    assert api_entry["message"] == journal_entry["MESSAGE"]
    assert (
        datetime.fromisoformat(api_entry["timestamp"])
        == journal_entry["__REALTIME_TIMESTAMP"]
    )
    assert api_entry.get("priority") == journal_entry.get("PRIORITY")
    assert api_entry.get("systemdUnit") == journal_entry.get("_SYSTEMD_UNIT")
    assert api_entry.get("systemdSlice") == journal_entry.get("_SYSTEMD_SLICE")


def take_from_journal(j, limit, next):
    entries = []
    for _ in range(0, limit):
        entry = next(j)
        if entry["MESSAGE"] != "":
            entries.append(entry)
    return entries


API_GET_LOGS_WITH_UP_BORDER = """
query TestQuery($upCursor: String) {
    logs {
        paginated(limit: 4, upCursor: $upCursor) {
            pageMeta {
                upCursor
                downCursor
            }
            entries {
                message
                timestamp
                priority
                systemdUnit
                systemdSlice
            }
        }
    }
}
"""

API_GET_LOGS_WITH_DOWN_BORDER = """
query TestQuery($downCursor: String) {
    logs {
        paginated(limit: 4, downCursor: $downCursor) {
            pageMeta {
                upCursor
                downCursor
            }
            entries {
                message
                timestamp
                priority
                systemdUnit
                systemdSlice
            }
        }
    }
}
"""


def test_graphql_get_logs_with_up_border(authorized_client):
    j = journal.Reader()
    j.seek_tail()

    # < - cursor
    # <- - log entry will be returned by API call.
    # ...
    # log <
    # log <-
    # log <-
    # log <-
    # log <-
    # log

    expected_entries = take_from_journal(j, 6, lambda j: j.get_previous())
    expected_entries.reverse()

    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_GET_LOGS_WITH_UP_BORDER,
            "variables": {"upCursor": expected_entries[0]["__CURSOR"]},
        },
    )
    assert response.status_code == 200

    expected_entries = expected_entries[1:-1]
    returned_entries = response.json()["data"]["logs"]["paginated"]["entries"]

    assert len(returned_entries) == len(expected_entries)

    for api_entry, journal_entry in zip(returned_entries, expected_entries):
        assert_log_entry_equals_to_journal_entry(api_entry, journal_entry)


def test_graphql_get_logs_with_down_border(authorized_client):
    j = journal.Reader()
    j.seek_head()
    j.get_next()

    # < - cursor
    # <- - log entry will be returned by API call.
    # log
    # log <-
    # log <-
    # log <-
    # log <-
    # log <
    # ...

    expected_entries = take_from_journal(j, 5, lambda j: j.get_next())

    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_GET_LOGS_WITH_DOWN_BORDER,
            "variables": {"downCursor": expected_entries[-1]["__CURSOR"]},
        },
    )
    assert response.status_code == 200

    expected_entries = expected_entries[:-1]
    returned_entries = response.json()["data"]["logs"]["paginated"]["entries"]

    assert len(returned_entries) == len(expected_entries)

    for api_entry, journal_entry in zip(returned_entries, expected_entries):
        assert_log_entry_equals_to_journal_entry(api_entry, journal_entry)


@pytest.mark.asyncio
async def test_websocket_subscription_for_logs(authorized_client):
    with authorized_client.websocket_connect(
        "/graphql", subprotocols=["graphql-transport-ws"]
    ) as websocket:
        init_graphql(websocket)
        websocket.send_json(
            {
                "id": "3aaa2445",
                "type": "subscribe",
                "payload": {
                    "query": "subscription TestSubscription { logEntries { message } }",
                },
            }
        )
        await asyncio.sleep(1)

        def read_until(message, limit=5):
            i = 0
            while i < limit:
                msg = websocket.receive_json()["payload"]["data"]["logEntries"][
                    "message"
                ]
                if msg == message:
                    return
                else:
                    i += 1
                    continue
            raise Exception("Failed to read websocket data, timeout")

        for i in range(0, 10):
            journal.send(f"Lorem ipsum number {i}")
            read_until(f"Lorem ipsum number {i}")