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

[((25, 1, 28, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(26, 4, 26, 23): '"""tested_frame_type"""', (27, 4, 27, 73): '[DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]'}, {}), "('tested_frame_type', [DateFrame.pomodoro_type,\n    DateFrame.break_type, DateFrame.pause_type])", False, 'import pytest\n'), ((128, 1, 131, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(129, 4, 129, 23): '"""tested_frame_type"""', (130, 4, 130, 73): '[DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]'}, {}), "('tested_frame_type', [DateFrame.pomodoro_type,\n    DateFrame.break_type, DateFrame.pause_type])", False, 'import pytest\n'), ((158, 1, 161, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(159, 4, 159, 23): '"""tested_frame_type"""', (160, 4, 160, 73): '[DateFrame.pomodoro_type, DateFrame.break_type, DateFrame.pause_type]'}, {}), "('tested_frame_type', [DateFrame.pomodoro_type,\n    DateFrame.break_type, DateFrame.pause_type])", False, 'import pytest\n'), ((13, 14, 13, 53), 'pytest.mark.django_db', 'pytest.mark.django_db', (), '', False, 'import pytest\n'), ((17, 19, 17, 96), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(17, 41, 17, 59): 'frames_application', (17, 61, 17, 95): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((30, 19, 30, 96), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(30, 41, 30, 59): 'frames_application', (30, 61, 30, 95): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((69, 19, 69, 96), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(69, 41, 69, 59): 'frames_application', (69, 61, 69, 95): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((134, 21, 134, 98), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(134, 43, 134, 61): 'frames_application', (134, 63, 134, 97): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((135, 21, 135, 116), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(135, 43, 135, 61): 'frames_application', (135, 63, 135, 115): 'f"""date_frames/{task_instance_in_second_project.id}/"""'}, {}), "(frames_application,\n    f'date_frames/{task_instance_in_second_project.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((163, 21, 163, 98), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(163, 43, 163, 61): 'frames_application', (163, 63, 163, 97): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((164, 21, 164, 98), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(164, 43, 164, 61): 'frames_application', (164, 63, 164, 97): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((199, 21, 199, 98), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(199, 43, 199, 61): 'frames_application', (199, 63, 199, 97): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((200, 21, 200, 98), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(200, 43, 200, 61): 'frames_application', (200, 63, 200, 97): 'f"""date_frames/{task_instance.id}/"""'}, {}), "(frames_application, f'date_frames/{task_instance.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((221, 19, 221, 115), 'channels.testing.WebsocketCommunicator', 'WebsocketCommunicator', ({(221, 41, 221, 59): 'frames_application', (221, 61, 221, 114): 'f"""date_frames/{task_instance_for_random_project.id}/"""'}, {}), "(frames_application,\n    f'date_frames/{task_instance_for_random_project.id}/')", False, 'from channels.testing import WebsocketCommunicator\n'), ((194, 8, 194, 44), 'pytest_lazyfixture.lazy_fixture', 'lazy_fixture', ({(194, 21, 194, 43): '"""pomodoro_in_progress"""'}, {}), "('pomodoro_in_progress')", False, 'from pytest_lazyfixture import lazy_fixture\n'), ((195, 8, 195, 41), 'pytest_lazyfixture.lazy_fixture', 'lazy_fixture', ({(195, 21, 195, 40): '"""pause_in_progress"""'}, {}), "('pause_in_progress')", False, 'from pytest_lazyfixture import lazy_fixture\n'), ((50, 17, 50, 68), 'channels.db.database_sync_to_async', 'database_sync_to_async', ({(50, 40, 50, 67): 'task_instance.frames.exists'}, {}), '(task_instance.frames.exists)', False, 'from channels.db import database_sync_to_async\n'), ((91, 21, 91, 66), 'channels.db.database_sync_to_async', 'database_sync_to_async', ({(91, 44, 91, 65): 'DateFrame.objects.get'}, {}), '(DateFrame.objects.get)', False, 'from channels.db import database_sync_to_async\n'), ((95, 18, 95, 63), 'channels.db.database_sync_to_async', 'database_sync_to_async', ({(95, 41, 95, 62): 'DateFrame.objects.get'}, {}), '(DateFrame.objects.get)', False, 'from channels.db import database_sync_to_async\n'), ((107, 10, 107, 55), 'channels.db.database_sync_to_async', 'database_sync_to_async', ({(107, 33, 107, 54): 'pause.refresh_from_db'}, {}), '(pause.refresh_from_db)', False, 'from channels.db import database_sync_to_async\n'), ((110, 10, 110, 58), 'channels.db.database_sync_to_async', 'database_sync_to_async', ({(110, 33, 110, 57): 'pomodoro.refresh_from_db'}, {}), '(pomodoro.refresh_from_db)', False, 'from channels.db import database_sync_to_async\n'), ((119, 10, 119, 58), 'channels.db.database_sync_to_async', 'database_sync_to_async', ({(119, 33, 119, 57): 'pomodoro.refresh_from_db'}, {}), '(pomodoro.refresh_from_db)', False, 'from channels.db import database_sync_to_async\n'), ((210, 11, 210, 51), 'json.loads', 'json.loads', ({(210, 22, 210, 50): "notification_message['text']"}, {}), "(notification_message['text'])", False, 'import json\n'), ((34, 17, 34, 68), 'channels.db.database_sync_to_async', 'database_sync_to_async', ({(34, 40, 34, 67): 'task_instance.frames.exists'}, {}), '(task_instance.frames.exists)', False, 'from channels.db import database_sync_to_async\n'), ((63, 40, 63, 93), 'pomodorr.frames.selectors.date_frame_selector.get_finished_date_frames_for_task', 'get_finished_date_frames_for_task', (), '', False, 'from pomodorr.frames.selectors.date_frame_selector import get_finished_date_frames_for_task\n'), ((123, 40, 123, 93), 'pomodorr.frames.selectors.date_frame_selector.get_finished_date_frames_for_task', 'get_finished_date_frames_for_task', (), '', False, 'from pomodorr.frames.selectors.date_frame_selector import get_finished_date_frames_for_task\n')]