import inspect
import logging
import sys
from typing import Any

from fastapi import Request
from open_webui.env import GLOBAL_LOG_LEVEL
from open_webui.models.functions import Functions
from open_webui.models.users import UserModel
from open_webui.socket.main import get_event_call, get_event_emitter
from open_webui.utils.middleware import process_tool_result
from open_webui.utils.models import get_all_models
from open_webui.utils.plugin import get_function_module_from_cache

logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
log = logging.getLogger(__name__)


async def chat_action(request: Request, action_id: str, form_data: dict, user: Any):
    if '.' in action_id:
        action_id, sub_action_id = action_id.split('.')
    else:
        sub_action_id = None

    action = await Functions.get_function_by_id(action_id)
    if not action:
        raise Exception(f'Action not found: {action_id}')

    if not request.app.state.MODELS:
        await get_all_models(request, user=user)

    if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'):
        models = {
            request.state.model['id']: request.state.model,
        }
    else:
        models = request.app.state.MODELS

    data = form_data
    model_id = data['model']

    if model_id not in models:
        raise Exception('Model not found')
    model = models[model_id]

    __event_emitter__ = await get_event_emitter(
        {
            'chat_id': data['chat_id'],
            'message_id': data['id'],
            'session_id': data['session_id'],
            'user_id': user.id,
        }
    )
    __event_call__ = await get_event_call(
        {
            'chat_id': data['chat_id'],
            'message_id': data['id'],
            'session_id': data['session_id'],
            'user_id': user.id,
        }
    )

    function_module, _, _ = await get_function_module_from_cache(request, action_id)

    if hasattr(function_module, 'valves') and hasattr(function_module, 'Valves'):
        valves = await Functions.get_function_valves_by_id(action_id)
        function_module.valves = function_module.Valves(**(valves if valves else {}))

    if hasattr(function_module, 'action'):
        try:
            action = function_module.action

            # Get the signature of the function
            sig = inspect.signature(action)
            params = {'body': data}

            # Extra parameters to be passed to the function
            extra_params = {
                '__model__': model,
                '__id__': sub_action_id if sub_action_id is not None else action_id,
                '__event_emitter__': __event_emitter__,
                '__event_call__': __event_call__,
                '__request__': request,
            }

            # Add extra params in contained in function signature
            for key, value in extra_params.items():
                if key in sig.parameters:
                    params[key] = value

            if '__user__' in sig.parameters:
                __user__ = user.model_dump() if isinstance(user, UserModel) else {}

                try:
                    if hasattr(function_module, 'UserValves'):
                        __user__['valves'] = function_module.UserValves(
                            **await Functions.get_user_valves_by_id_and_user_id(action_id, user.id)
                        )
                except Exception as e:
                    log.exception(f'Failed to get user values: {e}')

                params = {**params, '__user__': __user__}

            if inspect.iscoroutinefunction(action):
                data = await action(**params)
            else:
                data = action(**params)

            # Process action result for Rich UI embeds (HTMLResponse, tuple with headers)
            processed_result, _, action_embeds = await process_tool_result(
                request,
                action_id,
                data,
                'action',
            )

            if action_embeds:
                await __event_emitter__(
                    {
                        'type': 'embeds',
                        'data': {
                            'embeds': action_embeds,
                        },
                    }
                )
                # Replace data with the processed status dict so we don't
                # try to serialize the raw HTMLResponse / tuple back to the client
                data = processed_result

        except Exception as e:
            raise Exception(f'Error: {e}')

    return data
