import asyncio
import json

import pytest
from fastapi import status
from fastapi.testclient import TestClient

from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.action import ActionType
from cooperative_cuisine.game_server import (
    app,
    environment_handler,
    CreateEnvironmentConfig,
    ManageEnv,
)
from cooperative_cuisine.server_results import CreateEnvResult
from cooperative_cuisine.state_representation import StateRepresentation

environment_handler.extend_allowed_manager(["123"])


@pytest.fixture
def create_env_config():
    layout_path = ROOT_DIR / "configs" / "layouts" / "tutorial.layout"
    environment_config_path = ROOT_DIR / "configs" / "tutorial_env_config.yaml"
    item_info_path = ROOT_DIR / "configs" / "item_info.yaml"
    with open(item_info_path, "r") as file:
        item_info = file.read()
    with open(layout_path, "r") as file:
        layout = file.read()
    with open(environment_config_path, "r") as file:
        environment_config = file.read()

    return CreateEnvironmentConfig(
        manager_id="123",
        number_players=1,
        environment_settings={"all_player_can_pause_game": False},
        item_info_config=item_info,
        layout_config=layout,
        environment_config=environment_config,
        seed=123,
    )


def test_create_env(create_env_config):
    with TestClient(app) as client:
        res = client.post(
            "/manage/create_env/",
            json=create_env_config.model_dump(mode="json"),
        )

    assert res.status_code == status.HTTP_200_OK
    create_env_result = CreateEnvResult(**res.json())
    assert len(create_env_result["player_info"]) == 1
    assert isinstance(create_env_result["env_id"], str)


def test_invalid_manager_id_create_env(create_env_config):
    create_env_config.manager_id = "!"
    with TestClient(app) as client:
        res = client.post(
            "/manage/create_env/",
            json=create_env_config.model_dump(mode="json"),
        )
    assert res.status_code == status.HTTP_403_FORBIDDEN
    assert res.json() == {"detail": "Manager ID not known/registered."}


def test_invalid_create_env_config(create_env_config):
    create_env_config.number_players = -1
    with TestClient(app) as client:
        res = client.post(
            "/manage/create_env/",
            json=create_env_config.model_dump(mode="json"),
        )
    assert res.status_code == status.HTTP_409_CONFLICT
    assert res.json() == {"detail": "Number players need to be positive."}


def test_stop_env(create_env_config):
    with TestClient(app) as client:
        res = client.post(
            "/manage/create_env/",
            json=create_env_config.model_dump(mode="json"),
        )

    with TestClient(app) as client:
        res = client.post(
            "/manage/stop_env/",
            json=ManageEnv(
                manager_id="123", env_id=res.json()["env_id"], reason="test"
            ).model_dump(mode="json"),
        )

    assert res.status_code == status.HTTP_200_OK
    with TestClient(app) as client:
        res = client.post(
            "/manage/stop_env/",
            json=ManageEnv(manager_id="123", env_id="123456", reason="test").model_dump(
                mode="json"
            ),
        )

    assert res.status_code == status.HTTP_403_FORBIDDEN


def test_websocket(create_env_config):
    with TestClient(app) as client:
        environment_handler.envs = {}
        res = client.post(
            "/manage/create_env/",
            json=create_env_config.model_dump(mode="json"),
        )
        player_hash = res.json()["player_info"]["0"]["player_hash"]
        loop = asyncio.new_event_loop()
        task = loop.create_task(environment_handler.environment_steps())
        try:
            with client.websocket_connect(
                f"/ws/player/{res.json()['player_info']['0']['client_id']}"
            ) as websocket:
                assert environment_handler.check_all_players_connected(
                    res.json()["env_id"]
                )
                websocket.send_json({"player_hash": player_hash, "type": "ready"})
                assert websocket.receive_json() == {
                    "request_type": "ready",
                    "msg": f"ready accepted",
                    "status": 200,
                    "player_hash": player_hash,
                }
                loop.run_until_complete(asyncio.sleep(0.001))
                websocket.send_json({"player_hash": player_hash, "type": "get_state"})
                state = websocket.receive_json()
                assert state["all_players_ready"]
                del state["all_players_ready"]
                StateRepresentation.model_validate_json(json_data=json.dumps(state))

                websocket.send_json(
                    {
                        "player_hash": player_hash,
                        "type": "action",
                        "action": {
                            "player": "0",
                            "action_type": ActionType.PICK_UP_DROP.value,
                            "action_data": None,
                        },
                    }
                )
                assert websocket.receive_json() == {
                    "request_type": "action",
                    "status": 200,
                    "msg": f"action accepted",
                    "player_hash": player_hash,
                }

                assert (
                    len(
                        environment_handler.list_not_ready_players(res.json()["env_id"])
                    )
                    == 0
                )
                assert (
                    len(
                        environment_handler.list_not_connected_players(
                            res.json()["env_id"]
                        )
                    )
                    == 0
                )
        finally:
            task.cancel()
            loop.close()


def test_websocket_wrong_inputs(create_env_config):
    with TestClient(app) as client:
        environment_handler.envs = {}
        res = client.post(
            "/manage/create_env/",
            json=create_env_config.model_dump(mode="json"),
        )
        player_hash = res.json()["player_info"]["0"]["player_hash"]
        wrong_player_hash = player_hash + "-------"
        loop = asyncio.new_event_loop()
        task = loop.create_task(environment_handler.environment_steps())
        assert (
            len(environment_handler.list_not_connected_players(res.json()["env_id"]))
            == 1
        )
        try:
            with client.websocket_connect(
                f"/ws/player/{res.json()['player_info']['0']['client_id']}"
            ) as websocket:
                assert (
                    len(
                        environment_handler.list_not_ready_players(res.json()["env_id"])
                    )
                    == 1
                )
                assert (
                    len(
                        environment_handler.list_not_connected_players(
                            res.json()["env_id"]
                        )
                    )
                    == 0
                )

                websocket.send_json({"player_hash": wrong_player_hash, "type": "ready"})
                assert websocket.receive_json() == {
                    "request_type": "ready",
                    "msg": f"ready not accepted",
                    "status": 400,
                    "player_hash": wrong_player_hash,
                }
                loop.run_until_complete(asyncio.sleep(0.001))
                websocket.send_json(
                    {"player_hash": wrong_player_hash, "type": "get_state"}
                )
                state = websocket.receive_json()
                assert state == {
                    "request_type": "get_state",
                    "status": 400,
                    "msg": "player hash unknown",
                    "player_hash": None,
                }

                websocket.send_json(
                    {
                        "player_hash": wrong_player_hash,
                        "type": "action",
                        "action": {
                            "player": "0",
                            "action_type": ActionType.PICK_UP_DROP.value,
                            "action_data": None,
                        },
                    }
                )
                assert websocket.receive_json() == {
                    "request_type": "action",
                    "status": 400,
                    "msg": f"action not accepted",
                    "player_hash": wrong_player_hash,
                }

                websocket.send_json(
                    {
                        "player_hash": wrong_player_hash,
                        "type": "delta_v",
                        "action": {
                            "player": "0",
                            "action_type": ActionType.PICK_UP_DROP.value,
                            "action_data": None,
                        },
                    }
                )
                assert websocket.receive_json()["status"] == 400

                websocket.send_json(
                    {
                        "player_hash": wrong_player_hash,
                        "type": "action",
                    }
                )
                assert websocket.receive_json()["status"] == 400

                assert (
                    len(
                        environment_handler.list_not_ready_players(res.json()["env_id"])
                    )
                    == 1
                )
                assert (
                    len(
                        environment_handler.list_not_connected_players(
                            res.json()["env_id"]
                        )
                    )
                    == 0
                )

        finally:
            task.cancel()
            loop.close()


def test_root():
    with TestClient(app) as client:
        res = client.get("/")
        assert res.status_code == status.HTTP_200_OK
        assert res.json() == {"Cooperative": "Cuisine"}