#
# Copyright 2026 The Dapr Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from contextlib import AsyncExitStack
import logging

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.client.sse import sse_client
from mcp.client.streamable_http import streamable_http_client
from mcp.client.websocket import websocket_client
from httpx import AsyncClient

from dapr_agents.types.tools import (
    SseServerParameters,
    StreamableHTTPServerParameters,
    WebSocketServerParameters,
)

logger = logging.getLogger(__name__)


async def start_stdio_session(
    params: dict, stack: AsyncExitStack = None
) -> ClientSession:
    """
    Start a session with MCP server using stdio transport.

    Args:
        params: Dict with keys 'command', 'args', 'env', 'cwd'
        stack: AsyncExitStack for resource management

    Returns:
        An initialized MCP client session

    Raises:
        Exception: If connection fails
    """
    server_params = StdioServerParameters(**params)
    try:
        read_stream, write_stream = await stack.enter_async_context(
            stdio_client(server_params)
        )
        session = await stack.enter_async_context(
            ClientSession(read_stream, write_stream)
        )
        logger.debug("Stdio connection established successfully")
        return session
    except Exception as e:
        logger.error(f"Failed to establish stdio connection: {str(e)}")
        raise


async def start_sse_session(
    params: dict, stack: AsyncExitStack = None
) -> ClientSession:
    """
    Start a session with MCP server using Server-Sent Events (SSE) transport.

    Args:
        params: Dict with keys 'url', 'headers', 'timeout', 'sse_read_timeout'
        stack: AsyncExitStack for resource management

    Returns:
        An initialized MCP client session

    Raises:
        Exception: If connection fails
    """
    server_params = SseServerParameters(**params)
    try:
        read_stream, write_stream = await stack.enter_async_context(
            sse_client(
                url=server_params.url,
                headers=server_params.headers,
                timeout=server_params.timeout,
                sse_read_timeout=server_params.sse_read_timeout,
            )
        )
        session = await stack.enter_async_context(
            ClientSession(read_stream, write_stream)
        )
        logger.debug("SSE connection established successfully")
        return session
    except Exception as e:
        logger.error(f"Failed to establish SSE connection: {str(e)}")
        raise


async def start_streamable_http_session(
    params: dict, stack: AsyncExitStack = None
) -> ClientSession:
    """
    Start a session with MCP server using streamable HTTP transport.

    Args:
        params: Dict with keys 'url', 'headers', 'timeout', 'sse_read_timeout', 'terminate_on_close'
        stack: AsyncExitStack for resource management

    Returns:
        An initialized MCP client session

    Raises:
        Exception: If connection fails
    """
    server_params = StreamableHTTPServerParameters(**params)
    try:
        (
            read_stream,
            write_stream,
            _,
        ) = await stack.enter_async_context(
            streamable_http_client(
                url=server_params.url,
                http_client=AsyncClient(
                    timeout=server_params.timeout.seconds,
                    headers=server_params.headers,
                ),
                terminate_on_close=server_params.terminate_on_close,
            )
        )
        session = await stack.enter_async_context(
            ClientSession(read_stream, write_stream)
        )
        logger.debug("streamable_http connection established successfully")
        return session
    except Exception as e:
        logger.error(f"Failed to establish streamable_http connection: {str(e)}")
        raise


async def start_websocket_session(
    params: dict, stack: AsyncExitStack = None
) -> ClientSession:
    """
    Start a session with MCP server using websocket transport.

    Args:
        params: Dict with key 'url'
        stack: AsyncExitStack for resource management

    Returns:
        An initialized MCP client session

    Raises:
        Exception: If connection fails
    """
    server_params = WebSocketServerParameters(**params)
    try:
        read_stream, write_stream = await stack.enter_async_context(
            websocket_client(server_params.url)
        )
        session = await stack.enter_async_context(
            ClientSession(read_stream, write_stream)
        )
        logger.debug("Websocket connection established successfully")
        return session
    except Exception as e:
        logger.error(f"Failed to establish websocket connection: {str(e)}")
        raise


async def start_transport_session(
    transport: str, params: dict, stack: AsyncExitStack = None
) -> ClientSession:
    """
    Unified entry point to start a session with an MCP server using any supported transport.
    Dispatches to the correct transport session helper based on the transport type and params.

    Args:
        transport: The transport type (e.g., 'stdio', 'sse', 'streamable_http', 'websocket').
        params: Dict of transport-specific parameters.
        stack: AsyncExitStack for resource management.

    Returns:
        An initialized MCP client session.

    Raises:
        ValueError: If the transport type is unsupported or params are invalid.
    """
    if not transport:
        raise ValueError("Missing 'transport' argument")
    if transport == "stdio":
        return await start_stdio_session(params, stack=stack)
    elif transport == "sse":
        return await start_sse_session(params, stack=stack)
    elif transport == "streamable_http":
        return await start_streamable_http_session(params, stack=stack)
    elif transport == "websocket":
        return await start_websocket_session(params, stack=stack)
    else:
        raise ValueError(f"Unsupported transport: {transport}")
