import json

import pytest
from channels.db import database_sync_to_async
from channels.testing import WebsocketCommunicator
from pytest_lazyfixture import lazy_fixture

from pomodorr.frames import statuses
from pomodorr.frames.models import DateFrame
from pomodorr.frames.routing import frames_application
from pomodorr.frames.selectors.date_frame_selector import get_finished_date_frames_for_task

pytestmark = [pytest.mark.django_db(transaction=True), pytest.mark.asyncio]


async def test_connect_websocket(task_instance, active_user):
    communicator = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator.scope['user'] = active_user
    connected, _ = await communicator.connect()

    assert connected
    await communicator.disconnect()


@pytest.mark.parametrize(
    'tested_frame_type',
    [DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]
)
async def test_start_and_finish_date_frame(tested_frame_type, task_instance, active_user):
    communicator = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator.scope['user'] = active_user
    await communicator.connect()

    assert await database_sync_to_async(task_instance.frames.exists)() is False

    await communicator.send_json_to({
        'type': 'frame_start',
        'frame_type': tested_frame_type
    })

    response = await communicator.receive_json_from()

    assert response['level'] == statuses.MESSAGE_LEVEL_CHOICES[statuses.LEVEL_TYPE_SUCCESS]
    assert response['code'] == statuses.LEVEL_TYPE_SUCCESS
    assert response['action'] == statuses.MESSAGE_FRAME_ACTION_CHOICES[statuses.FRAME_ACTION_STARTED]

    started_date_frame_id = response['data']['date_frame_id']

    assert started_date_frame_id is not None
    assert await database_sync_to_async(task_instance.frames.exists)()

    await communicator.send_json_to({
        'type': 'frame_finish',
        'date_frame_id': started_date_frame_id
    })

    response = await communicator.receive_json_from()

    assert response['level'] == statuses.MESSAGE_LEVEL_CHOICES[statuses.LEVEL_TYPE_SUCCESS]
    assert response['code'] == statuses.LEVEL_TYPE_SUCCESS
    assert response['action'] == statuses.MESSAGE_FRAME_ACTION_CHOICES[statuses.FRAME_ACTION_FINISHED]

    assert await database_sync_to_async(get_finished_date_frames_for_task(task=task_instance).exists)()

    await communicator.disconnect()


async def test_start_and_finish_pomodoro_with_pause_inside(task_instance, active_user):
    communicator = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator.scope['user'] = active_user
    await communicator.connect()

    await communicator.send_json_to({
        'type': 'frame_start',
        'frame_type': DateFrame.pomodoro_type
    })

    pomodoro_started_response = await communicator.receive_json_from()
    assert pomodoro_started_response['action'] == statuses.MESSAGE_FRAME_ACTION_CHOICES[statuses.FRAME_ACTION_STARTED]

    started_pomodoro_id = pomodoro_started_response['data']['date_frame_id']

    await communicator.send_json_to({
        'type': 'frame_start',
        'frame_type': DateFrame.pause_type
    })

    pause_started_response = await communicator.receive_json_from()
    assert pause_started_response['action'] == statuses.MESSAGE_FRAME_ACTION_CHOICES[statuses.FRAME_ACTION_STARTED]

    pomodoro = await database_sync_to_async(DateFrame.objects.get)(id=started_pomodoro_id)
    assert pomodoro.end is None  # check if pomodoro hasn't been stopped by starting a pause date frame

    started_pause_id = pause_started_response['data']['date_frame_id']
    pause = await database_sync_to_async(DateFrame.objects.get)(id=started_pause_id)

    assert pause.end is None

    await communicator.send_json_to({
        'type': 'frame_finish',
        'date_frame_id': started_pause_id
    })

    pause_finished_response = await communicator.receive_json_from()
    assert pause_finished_response['action'] == statuses.MESSAGE_FRAME_ACTION_CHOICES[statuses.FRAME_ACTION_FINISHED]

    await database_sync_to_async(pause.refresh_from_db)()

    assert pause.end is not None  # pause should be finished here
    await database_sync_to_async(pomodoro.refresh_from_db)()
    assert pomodoro.end is None

    await communicator.send_json_to({
        'type': 'frame_finish',
        'date_frame_id': started_pomodoro_id
    })

    pomodoro_finished_response = await communicator.receive_json_from()
    await database_sync_to_async(pomodoro.refresh_from_db)()

    assert pomodoro.end is not None  # Only now the pomodoro is expected to be finished
    assert pomodoro_finished_response['action'] == statuses.MESSAGE_FRAME_ACTION_CHOICES[statuses.FRAME_ACTION_FINISHED]
    assert await database_sync_to_async(get_finished_date_frames_for_task(task=task_instance).count)() == 2

    await communicator.disconnect()


@pytest.mark.parametrize(
    'tested_frame_type',
    [DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]
)
async def test_channel_group_separation(tested_frame_type, active_user, task_instance,
                                        task_instance_in_second_project):
    communicator_1 = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator_2 = WebsocketCommunicator(frames_application, f'date_frames/{task_instance_in_second_project.id}/')
    communicator_1.scope['user'] = active_user
    communicator_2.scope['user'] = active_user
    communicator_1_connected, _ = await communicator_1.connect()
    communicator_2_connected, _ = await communicator_2.connect()

    assert communicator_1_connected
    assert communicator_2_connected
    assert await communicator_1.receive_nothing()
    assert await communicator_2.receive_nothing()

    await communicator_1.send_json_to({
        'type': 'frame_start',
        'frame_type': tested_frame_type
    })

    assert await communicator_1.receive_nothing() is False
    assert await communicator_2.receive_nothing()

    await communicator_1.disconnect()
    await communicator_2.disconnect()


@pytest.mark.parametrize(
    'tested_frame_type',
    [DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]
)
async def test_connection_discarded_before_second_connection_established(tested_frame_type, active_user, task_instance):
    communicator_1 = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator_2 = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator_1.scope['user'] = active_user
    communicator_2.scope['user'] = active_user

    communicator_1_connected, _ = await communicator_1.connect()
    assert communicator_1_connected

    communicator_2_connected, _ = await communicator_2.connect()
    assert communicator_2_connected

    connection_close_response = await communicator_1.receive_output()

    assert connection_close_response['type'] == 'websocket.close'
    assert await communicator_1.receive_nothing()
    assert await communicator_2.receive_nothing()

    await communicator_2.send_json_to({
        'type': 'frame_start',
        'frame_type': tested_frame_type
    })

    assert await communicator_1.receive_nothing()
    assert await communicator_2.receive_nothing() is False

    await communicator_2.disconnect()


@pytest.mark.parametrize(
    'tested_frame_type',
    [
        lazy_fixture('pomodoro_in_progress'),
        lazy_fixture('pause_in_progress')
    ]
)
async def test_date_frame_force_finished_and_client_notified(tested_frame_type, active_user, task_instance):
    communicator_1 = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator_2 = WebsocketCommunicator(frames_application, f'date_frames/{task_instance.id}/')
    communicator_1.scope['user'] = active_user
    communicator_2.scope['user'] = active_user

    await communicator_1.connect()
    await communicator_2.connect()

    notification_message = await communicator_1.receive_output()

    assert notification_message['type'] == 'websocket.send'
    assert json.loads(notification_message['text'])['action'] == statuses.MESSAGE_FRAME_ACTION_CHOICES[
        statuses.FRAME_ACTION_FORCE_TERMINATED]

    connection_close_response = await communicator_1.receive_output()
    assert connection_close_response['type'] == 'websocket.close'

    await communicator_1.disconnect()
    await communicator_2.disconnect()


async def test_channel_group_permission(task_instance_for_random_project, active_user):
    communicator = WebsocketCommunicator(frames_application, f'date_frames/{task_instance_for_random_project.id}/')
    communicator.scope['user'] = active_user
    connected, _ = await communicator.connect()

    assert connected is False

[((791, 910), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""tested_frame_type"""', '[DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]'], {}), "('tested_frame_type', [DateFrame.pomodoro_type,\n    DateFrame.break_type, DateFrame.pause_type])\n", (814, 910), False, 'import pytest\n'), ((4934, 5053), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""tested_frame_type"""', '[DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]'], {}), "('tested_frame_type', [DateFrame.pomodoro_type,\n    DateFrame.break_type, DateFrame.pause_type])\n", (4957, 5053), False, 'import pytest\n'), ((6143, 6262), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""tested_frame_type"""', '[DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]'], {}), "('tested_frame_type', [DateFrame.pomodoro_type,\n    DateFrame.break_type, DateFrame.pause_type])\n", (6166, 6262), False, 'import pytest\n'), ((414, 453), 'pytest.mark.django_db', 'pytest.mark.django_db', ([], {'transaction': '(True)'}), '(transaction=True)\n', (435, 453), False, 'import pytest\n'), ((559, 636), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (580, 636), False, 'from channels.testing import WebsocketCommunicator\n'), ((1027, 1104), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (1048, 1104), False, 'from channels.testing import WebsocketCommunicator\n'), ((2556, 2633), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (2577, 2633), False, 'from channels.testing import WebsocketCommunicator\n'), ((5242, 5319), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (5263, 5319), False, 'from channels.testing import WebsocketCommunicator\n'), ((5341, 5440), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance_in_second_project.id}/"""'], {}), "(frames_application,\n    f'date_frames/{task_instance_in_second_project.id}/')\n", (5362, 5440), False, 'from channels.testing import WebsocketCommunicator\n'), ((6411, 6488), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (6432, 6488), False, 'from channels.testing import WebsocketCommunicator\n'), ((6510, 6587), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (6531, 6587), False, 'from channels.testing import WebsocketCommunicator\n'), ((7677, 7754), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (7698, 7754), False, 'from channels.testing import WebsocketCommunicator\n'), ((7776, 7853), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance.id}/"""'], {}), "(frames_application, f'date_frames/{task_instance.id}/')\n", (7797, 7853), False, 'from channels.testing import WebsocketCommunicator\n'), ((8621, 8721), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', (['frames_application', 'f"""date_frames/{task_instance_for_random_project.id}/"""'], {}), "(frames_application,\n    f'date_frames/{task_instance_for_random_project.id}/')\n", (8642, 8721), False, 'from channels.testing import WebsocketCommunicator\n'), ((7459, 7495), 'pytest_lazyfixture.lazy_fixture', 'lazy_fixture', (['"""pomodoro_in_progress"""'], {}), "('pomodoro_in_progress')\n", (7471, 7495), False, 'from pytest_lazyfixture import lazy_fixture\n'), ((7505, 7538), 'pytest_lazyfixture.lazy_fixture', 'lazy_fixture', (['"""pause_in_progress"""'], {}), "('pause_in_progress')\n", (7517, 7538), False, 'from pytest_lazyfixture import lazy_fixture\n'), ((1816, 1867), 'channels.db.database_sync_to_async', 'database_sync_to_async', (['task_instance.frames.exists'], {}), '(task_instance.frames.exists)\n', (1838, 1867), False, 'from channels.db import database_sync_to_async\n'), ((3431, 3476), 'channels.db.database_sync_to_async', 'database_sync_to_async', (['DateFrame.objects.get'], {}), '(DateFrame.objects.get)\n', (3453, 3476), False, 'from channels.db import database_sync_to_async\n'), ((3695, 3740), 'channels.db.database_sync_to_async', 'database_sync_to_async', (['DateFrame.objects.get'], {}), '(DateFrame.objects.get)\n', (3717, 3740), False, 'from channels.db import database_sync_to_async\n'), ((4111, 4156), 'channels.db.database_sync_to_async', 'database_sync_to_async', (['pause.refresh_from_db'], {}), '(pause.refresh_from_db)\n', (4133, 4156), False, 'from channels.db import database_sync_to_async\n'), ((4236, 4284), 'channels.db.database_sync_to_async', 'database_sync_to_async', (['pomodoro.refresh_from_db'], {}), '(pomodoro.refresh_from_db)\n', (4258, 4284), False, 'from channels.db import database_sync_to_async\n'), ((4525, 4573), 'channels.db.database_sync_to_async', 'database_sync_to_async', (['pomodoro.refresh_from_db'], {}), '(pomodoro.refresh_from_db)\n', (4547, 4573), False, 'from channels.db import database_sync_to_async\n'), ((8157, 8197), 'json.loads', 'json.loads', (["notification_message['text']"], {}), "(notification_message['text'])\n", (8167, 8197), False, 'import json\n'), ((1201, 1252), 'channels.db.database_sync_to_async', 'database_sync_to_async', (['task_instance.frames.exists'], {}), '(task_instance.frames.exists)\n', (1223, 1252), False, 'from channels.db import database_sync_to_async\n'), ((2346, 2399), 'pomodorr.frames.selectors.date_frame_selector.get_finished_date_frames_for_task', 'get_finished_date_frames_for_task', ([], {'task': 'task_instance'}), '(task=task_instance)\n', (2379, 2399), False, 'from pomodorr.frames.selectors.date_frame_selector import get_finished_date_frames_for_task\n'), ((4826, 4879), 'pomodorr.frames.selectors.date_frame_selector.get_finished_date_frames_for_task', 'get_finished_date_frames_for_task', ([], {'task': 'task_instance'}), '(task=task_instance)\n', (4859, 4879), False, 'from pomodorr.frames.selectors.date_frame_selector import get_finished_date_frames_for_task\n')]