import asyncio
from typing import Annotated

import sniffio

from cyclopts import App, Parameter


def test_async_handler(app):
    @app.command(name="command")
    async def async_handler():
        assert sniffio.current_async_library() == "asyncio"
        return "Async handler works"

    assert app("command") == "Async handler works"


def test_async_handler_with_subcommand_works(app):
    sub_app = App(name="foo")
    app.command(sub_app)

    @sub_app.command(name="bar")
    async def async_handler():
        assert sniffio.current_async_library() == "asyncio"
        return "Async handler works"

    assert app("foo bar") == "Async handler works"


def test_handler(app):
    @app.command(name="command")
    def sync_handler():
        return "Sync handler works"

    assert app("command") == "Sync handler works"


def test_async_meta_with_async_command(app):
    results = []

    @app.command
    async def async_command(value: int):
        await asyncio.sleep(0)  # Simulate async work
        result = f"Async command executed with {value}"
        results.append(result)
        return result

    @app.meta.default
    async def launcher(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]):
        await asyncio.sleep(0)  # Simulate async initialization
        results.append("Meta initialized")
        result = await app.run_async(tokens)  # Use run_async when inside an async context
        results.append("Meta finished")
        return result

    result = app.meta(["async-command", "42"])
    assert result == "Async command executed with 42"
    assert results == ["Meta initialized", "Async command executed with 42", "Meta finished"]


def test_async_meta_with_sync_command(app):
    results = []

    @app.command
    def sync_command(value: int):
        result = f"Sync command executed with {value}"
        results.append(result)
        return result

    @app.meta.default
    async def launcher(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]):
        await asyncio.sleep(0)  # Simulate async initialization
        results.append("Meta initialized")
        # Synchronous commands should also work from async meta
        result = await app.run_async(tokens)  # Use run_async when inside an async context
        results.append("Meta finished")
        return result

    result = app.meta(["sync-command", "42"])
    assert result == "Sync command executed with 42"
    assert results == ["Meta initialized", "Sync command executed with 42", "Meta finished"]


def test_async_meta_with_nested_async(app):
    results = []

    @app.default
    async def default_handler():
        await asyncio.sleep(0)
        results.append("Default handler")
        return "Default result"

    @app.meta.default
    async def meta(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]):
        await asyncio.sleep(0)
        results.append("Meta handler")
        result = await app.run_async(tokens)  # Use run_async when inside an async context
        results.append("Meta complete")
        return result

    result = app.meta([])
    assert result == "Default result"
    assert results == ["Meta handler", "Default handler", "Meta complete"]


def test_run_async_inherits_app_parameters():
    """Test that run_async inherits parameters from App instance when not specified."""
    app = App(
        print_error=False,
        exit_on_error=False,
        verbose=True,
        backend="asyncio",
        result_action="return_value",
    )

    @app.default
    async def handler():
        return "test"

    # These should inherit from the app instance
    async def run_test():
        result = await app.run_async([])
        return result

    result = asyncio.run(run_test())
    assert result == "test"


def test_run_async_overrides_app_parameters():
    """Test that run_async can override App instance parameters."""
    app = App(
        print_error=True,
        exit_on_error=True,
        verbose=False,
        result_action="return_value",
    )

    @app.default
    async def handler():
        return "test"

    # Override the app-level settings
    async def run_test():
        result = await app.run_async(
            [],
            print_error=False,
            exit_on_error=False,
            verbose=True,
        )
        return result

    result = asyncio.run(run_test())
    assert result == "test"


def test_run_async_backend_parameter(app):
    """Test that run_async accepts and respects backend parameter."""

    @app.default
    async def handler():
        # Verify we're using asyncio
        assert sniffio.current_async_library() == "asyncio"
        return "test"

    async def run_test():
        # Explicitly specify asyncio backend
        result = await app.run_async([], backend="asyncio")
        return result

    result = asyncio.run(run_test())
    assert result == "test"


def test_run_async_with_none_parameters(app):
    """Test that run_async handles None parameters correctly."""

    @app.default
    async def handler():
        return "test"

    async def run_test():
        # All None parameters should inherit from app defaults
        result = await app.run_async(
            [],
            print_error=None,
            exit_on_error=None,
            verbose=None,
            backend=None,
        )
        return result

    result = asyncio.run(run_test())
    assert result == "test"


def test_run_async_with_result_action(app):
    """Test that run_async properly handles result_action parameter."""

    @app.default
    async def handler():
        return 42

    async def run_test():
        # With result_action="return_value", should return the value directly
        result = await app.run_async([], result_action="return_value")
        return result

    result = asyncio.run(run_test())
    assert result == 42
