# Copyright (C) DATADVANCE, 2010-2020
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Test the `confirm_subscriptions` setting."""

# NOTE: In this file we use `strict_ordering=True` to simplify testing.

import uuid

import graphene
import pytest

import channels_graphql_ws


@pytest.mark.asyncio
async def test_confirmation_enabled(gql):
    """Test subscription confirmation message received when enabled."""

    print("Establish WebSocket GraphQL connections with subscription confirmation.")

    client = gql(
        mutation=Mutation,
        subscription=Subscription,
        consumer_attrs={"strict_ordering": True, "confirm_subscriptions": True},
    )
    await client.connect_and_init()

    print("Subscribe & check there is a subscription confirmation message.")

    sub_op_id = await client.send(
        msg_type="start",
        payload={
            "query": "subscription op_name { on_trigger { is_ok } }",
            "operationName": "op_name",
        },
    )

    resp = await client.receive(assert_id=sub_op_id, assert_type="data")
    assert resp == {"data": None}

    print("Trigger the subscription.")

    mut_op_id = await client.send(
        msg_type="start",
        payload={
            "query": """mutation op_name { trigger { is_ok } }""",
            "operationName": "op_name",
        },
    )
    await client.receive(assert_id=mut_op_id, assert_type="data")
    await client.receive(assert_id=mut_op_id, assert_type="complete")

    print("Check that subscription notification received.")

    resp = await client.receive(assert_id=sub_op_id, assert_type="data")
    assert resp["data"]["on_trigger"]["is_ok"] is True

    await client.assert_no_messages(
        "Unexpected message received at the end of the test!"
    )
    await client.finalize()


@pytest.mark.asyncio
async def test_confirmation_disabled(gql):
    """Test subscription confirmation message absent when disabled."""

    print("Establish WebSocket GraphQL connections w/o a subscription confirmation.")

    client = gql(
        mutation=Mutation,
        subscription=Subscription,
        consumer_attrs={"strict_ordering": True, "confirm_subscriptions": False},
    )
    await client.connect_and_init()

    print("Subscribe & check there is no subscription confirmation message.")

    sub_op_id = await client.send(
        msg_type="start",
        payload={
            "query": "subscription op_name { on_trigger { is_ok } }",
            "operationName": "op_name",
        },
    )

    await client.assert_no_messages("Subscribe responded with a message!")

    print("Trigger the subscription.")

    mut_op_id = await client.send(
        msg_type="start",
        payload={
            "query": """mutation op_name { trigger { is_ok } }""",
            "operationName": "op_name",
        },
    )
    await client.receive(assert_id=mut_op_id, assert_type="data")
    await client.receive(assert_id=mut_op_id, assert_type="complete")

    print("Check that subscription notification received.")

    resp = await client.receive(assert_id=sub_op_id, assert_type="data")
    assert resp == {"data": {"on_trigger": {"is_ok": True}}}

    await client.assert_no_messages(
        "Unexpected message received at the end of the test!"
    )
    await client.finalize()


@pytest.mark.asyncio
async def test_custom_confirmation_message(gql):
    """Test custom confirmation message."""

    print("Establish WebSocket GraphQL connections with a custom confirmation message.")

    expected_data = uuid.uuid4().hex
    expected_error = RuntimeError(uuid.uuid4().hex)

    client = gql(
        mutation=Mutation,
        subscription=Subscription,
        consumer_attrs={
            "strict_ordering": True,
            "confirm_subscriptions": True,
            "subscription_confirmation_message": {
                "data": expected_data,
                "errors": [expected_error],
            },
        },
    )
    await client.connect_and_init()

    print("Subscribe & check there is a subscription confirmation message.")

    sub_op_id = await client.send(
        msg_type="start",
        payload={
            "query": "subscription op_name { on_trigger { is_ok } }",
            "operationName": "op_name",
        },
    )

    with pytest.raises(channels_graphql_ws.GraphqlWsResponseError) as error:
        await client.receive(assert_id=sub_op_id, assert_type="data")
        expected_errors = [
            {"message": f"{type(expected_error).__name__}: {expected_error}"}
        ]
        assert error.errors == expected_errors, "Wrong confirmation errors received!"
        assert error.response == {
            "data": expected_data,
            "errors": expected_errors,
        }, "Wrong subscription confirmation message received!"

    print("Trigger the subscription.")

    mut_op_id = await client.send(
        msg_type="start",
        payload={
            "query": """mutation op_name { trigger { is_ok } }""",
            "operationName": "op_name",
        },
    )
    await client.receive(assert_id=mut_op_id, assert_type="data")
    await client.receive(assert_id=mut_op_id, assert_type="complete")

    print("Check that subscription notification received.")

    resp = await client.receive(assert_id=sub_op_id, assert_type="data")
    assert resp["data"]["on_trigger"]["is_ok"] is True

    await client.assert_no_messages(
        "Unexpected message received at the end of the test!"
    )
    await client.finalize()


# ------------------------------------------------------------------- TEST GRAPHQL SETUP


class Trigger(graphene.Mutation):
    """Trigger the subscription."""

    is_ok = graphene.Boolean()

    @staticmethod
    def mutate(root, info):
        """Trigger the subscription."""
        del root, info
        OnTrigger.broadcast()
        return Trigger(is_ok=True)


class OnTrigger(channels_graphql_ws.Subscription):
    """Simple subscription triggered by the `Trigger` mutation."""

    is_ok = graphene.Boolean()

    @staticmethod
    def publish(payload, info):
        """Send the subscription notification."""
        del payload, info
        return OnTrigger(is_ok=True)


class Subscription(graphene.ObjectType):
    """Root subscription."""

    on_trigger = OnTrigger.Field()


class Mutation(graphene.ObjectType):
    """Root mutation."""

    trigger = Trigger.Field()