import inspect
from contextlib import asynccontextmanager

import pytest
from fastapi import Request
from fastapi.responses import PlainTextResponse
from fastapi.testclient import TestClient
from gradio_client import Client

import gradio as gr
from gradio.route_utils import API_PREFIX


class TestServer:
    def test_server_launch_merges_lifespan_into_request_state(self):
        @asynccontextmanager
        async def lifespan(_):
            yield {"hello": "world"}

        server = gr.Server()

        @server.get("/state-check")
        def state_check(request: Request):
            return PlainTextResponse(request.state.hello)

        @server.api(name="echo")
        def echo(x: str) -> str:
            return x

        app, _, _ = server.launch(
            app_kwargs={"lifespan": lifespan}, prevent_thread_lock=True
        )
        try:
            with TestClient(app) as client:
                assert client.get("/state-check").text.strip() == "world"
        finally:
            gr.close_all()

    def test_server_launch_queue_and_api_predict(self):
        server = gr.Server()

        @server.api(name="echo")
        def echo(x: str) -> str:
            return x

        app, local_url, _ = server.launch(prevent_thread_lock=True)
        try:
            with TestClient(app) as client:
                status = client.get(f"{API_PREFIX}/queue/status")
                assert status.status_code == 200
                assert "queue_size" in status.json()

            client_g = Client(local_url)
            try:
                assert client_g.predict("hi", api_name="/echo") == "hi"
            finally:
                client_g.close()
        finally:
            gr.close_all()

    def test_server_launch_enables_mcp(self):
        server = gr.Server()

        @server.api(name="double")
        def double(word: str) -> str:
            """
            Doubles the input word.

            Parameters:
                word: The word to double
            Returns:
                The doubled word
            """
            return word * 2

        app, _, _ = server.launch(prevent_thread_lock=True, mcp_server=True)
        try:
            blocks = app.get_blocks()
            if getattr(blocks, "mcp_error", None):
                pytest.skip(blocks.mcp_error)
            assert blocks.mcp_server is True
            assert blocks.mcp_server_obj is not None
        finally:
            gr.close_all()

    def test_server_api_appears_in_gradio_api_info(self):
        server = gr.Server()

        @server.api(name="named_echo")
        def named_echo(msg: str) -> str:
            return msg

        app, _, _ = server.launch(prevent_thread_lock=True)
        try:
            with TestClient(app) as client:
                info = client.get(f"{API_PREFIX}/info").json()
            assert "/named_echo" in info.get("named_endpoints", {})
        finally:
            gr.close_all()


def test_server_launch_args_match_blocks_launch():
    launch_params = inspect.signature(gr.Blocks.launch).parameters
    server_params = inspect.signature(gr.Server.launch).parameters

    exception_list = {"self", "_app"}
    missing_params = []
    for param_name in launch_params:
        if param_name not in exception_list and param_name not in server_params:
            missing_params.append(param_name)

    assert not missing_params, (
        f"Parameters in Blocks.launch() but missing in Server.launch(): {missing_params}"
    )

    extra_params = []
    for param_name in server_params:
        if param_name not in launch_params and param_name != "self":
            extra_params.append(param_name)

    assert not extra_params, (
        f"Parameters in Server.launch() but not in Blocks.launch(): {extra_params}"
    )
