# from selfprivacy_api.graphql.subscriptions.jobs import JobSubscriptions
import pytest
import asyncio
from typing import Generator
from time import sleep

from starlette.testclient import WebSocketTestSession

from selfprivacy_api.jobs import Jobs
from selfprivacy_api.actions.api_tokens import ACTIVE_TOKEN_PROVIDER
from selfprivacy_api.graphql import IsAuthenticated

from tests.conftest import DEVICE_WE_AUTH_TESTS_WITH
from tests.test_jobs import jobs as empty_jobs

# We do not iterate through them yet
TESTED_SUBPROTOCOLS = ["graphql-transport-ws"]

JOBS_SUBSCRIPTION = """
jobUpdates {
    uid
    typeId
    name
    description
    status
    statusText
    progress
    createdAt
    updatedAt
    finishedAt
    error
    result
}
"""


def api_subscribe(websocket, id, subscription):
    websocket.send_json(
        {
            "id": id,
            "type": "subscribe",
            "payload": {
                "query": "subscription TestSubscription {" + subscription + "}",
            },
        }
    )


def connect_ws_authenticated(authorized_client) -> WebSocketTestSession:
    token = "Bearer " + str(DEVICE_WE_AUTH_TESTS_WITH["token"])
    return authorized_client.websocket_connect(
        "/graphql",
        subprotocols=TESTED_SUBPROTOCOLS,
        params={"token": token},
    )


def connect_ws_not_authenticated(client) -> WebSocketTestSession:
    return client.websocket_connect(
        "/graphql",
        subprotocols=TESTED_SUBPROTOCOLS,
        params={"token": "I like vegan icecream but it is not a valid token"},
    )


def init_graphql(websocket):
    websocket.send_json({"type": "connection_init", "payload": {}})
    ack = websocket.receive_json()
    assert ack == {"type": "connection_ack"}


@pytest.fixture
def authenticated_websocket(
    authorized_client,
) -> Generator[WebSocketTestSession, None, None]:
    # We use authorized_client only to have token in the repo, this client by itself is not enough to authorize websocket

    ValueError(ACTIVE_TOKEN_PROVIDER.get_tokens())
    with connect_ws_authenticated(authorized_client) as websocket:
        yield websocket


@pytest.fixture
def unauthenticated_websocket(client) -> Generator[WebSocketTestSession, None, None]:
    with connect_ws_not_authenticated(client) as websocket:
        yield websocket


def test_websocket_connection_bare(authorized_client):
    client = authorized_client
    with client.websocket_connect(
        "/graphql", subprotocols=["graphql-transport-ws", "graphql-ws"]
    ) as websocket:
        assert websocket is not None
        assert websocket.scope is not None


def test_websocket_graphql_init(authorized_client):
    client = authorized_client
    with client.websocket_connect(
        "/graphql", subprotocols=["graphql-transport-ws"]
    ) as websocket:
        websocket.send_json({"type": "connection_init", "payload": {}})
        ack = websocket.receive_json()
        assert ack == {"type": "connection_ack"}


def test_websocket_graphql_ping(authorized_client):
    client = authorized_client
    with client.websocket_connect(
        "/graphql", subprotocols=["graphql-transport-ws"]
    ) as websocket:
        # https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#ping
        websocket.send_json({"type": "ping", "payload": {}})
        pong = websocket.receive_json()
        assert pong == {"type": "pong"}


def test_websocket_subscription_minimal(authorized_client, authenticated_websocket):
    # Test a small endpoint that exists specifically for tests
    websocket = authenticated_websocket
    init_graphql(websocket)
    arbitrary_id = "3aaa2445"
    api_subscribe(websocket, arbitrary_id, "count")
    response = websocket.receive_json()
    assert response == {
        "id": arbitrary_id,
        "payload": {"data": {"count": 0}},
        "type": "next",
    }
    response = websocket.receive_json()
    assert response == {
        "id": arbitrary_id,
        "payload": {"data": {"count": 1}},
        "type": "next",
    }
    response = websocket.receive_json()
    assert response == {
        "id": arbitrary_id,
        "payload": {"data": {"count": 2}},
        "type": "next",
    }


def test_websocket_subscription_minimal_unauthorized(unauthenticated_websocket):
    websocket = unauthenticated_websocket
    init_graphql(websocket)
    arbitrary_id = "3aaa2445"
    api_subscribe(websocket, arbitrary_id, "count")

    response = websocket.receive_json()
    assert response == {
        "id": arbitrary_id,
        "payload": [{"message": IsAuthenticated.message}],
        "type": "error",
    }


async def read_one_job(websocket):
    # Bug? We only get them starting from the second job update
    # That's why we receive two jobs in the list them
    # The first update gets lost somewhere
    response = websocket.receive_json()
    return response


@pytest.mark.asyncio
async def test_websocket_subscription(authenticated_websocket, event_loop, empty_jobs):
    websocket = authenticated_websocket
    init_graphql(websocket)
    arbitrary_id = "3aaa2445"
    api_subscribe(websocket, arbitrary_id, JOBS_SUBSCRIPTION)

    future = asyncio.create_task(read_one_job(websocket))
    jobs = []
    jobs.append(Jobs.add("bogus", "bogus.bogus", "yyyaaaaayy it works"))
    sleep(0.5)
    jobs.append(Jobs.add("bogus2", "bogus.bogus", "yyyaaaaayy it works"))

    response = await future
    data = response["payload"]["data"]
    jobs_received = data["jobUpdates"]
    received_names = [job["name"] for job in jobs_received]
    for job in jobs:
        assert job.name in received_names

    assert len(jobs_received) == 2

    for job in jobs:
        for api_job in jobs_received:
            if (job.name) == api_job["name"]:
                assert api_job["uid"] == str(job.uid)
                assert api_job["typeId"] == job.type_id
                assert api_job["name"] == job.name
                assert api_job["description"] == job.description
                assert api_job["status"] == job.status
                assert api_job["statusText"] == job.status_text
                assert api_job["progress"] == job.progress
                assert api_job["createdAt"] == job.created_at.isoformat()
                assert api_job["updatedAt"] == job.updated_at.isoformat()
                assert api_job["finishedAt"] == None
                assert api_job["error"] == None
                assert api_job["result"] == None


def test_websocket_subscription_unauthorized(unauthenticated_websocket):
    websocket = unauthenticated_websocket
    init_graphql(websocket)
    id = "3aaa2445"
    api_subscribe(websocket, id, JOBS_SUBSCRIPTION)

    response = websocket.receive_json()
    # I do not really know why strawberry gives more info on this
    # One versus the counter
    payload = response["payload"][0]
    assert isinstance(payload, dict)
    assert "locations" in payload.keys()
    # It looks like this 'locations': [{'column': 32, 'line': 1}]
    # We cannot test locations feasibly
    del payload["locations"]
    assert response == {
        "id": id,
        "payload": [{"message": IsAuthenticated.message, "path": ["jobUpdates"]}],
        "type": "error",
    }