from typing import List, Any, Dict, Optional
import traceback

from fastapi import Request
from loguru import logger

from fastpluggy.core.global_registry import GlobalRegistry
from fastpluggy.core.tools.inspect_tools import call_with_injection
from fastpluggy.core.widgets.fastpluggy_integration import FastPluggyWidgets


class ViewBuilder:
    """
    ViewBuilder with FastPluggy widget registry integration.
    """

    def __init__(self):
        self.templates = None

    def generate(
        self,
        request: Request,
        widgets: Optional[List[Any]] = None,
        top_title: Optional[str] = None,
        title: Optional[str] = None,
        **kwargs
    ) -> Any:
        if widgets is None:
            widgets = []

        # Inject globally-registered widgets into the page widget list.
        # The current widget list is passed as context so that widgets like
        # DebugPanelWidget can receive the page's widgets as `items`.
        self.inject_global_widgets(widgets, request, tag='#ROOT#')

        # Get widget registry data for templates
        widget_registry = FastPluggyWidgets.get_widget_registry()

        # Build template context
        context = {
            "request": request,
            "title": title,
            **self._build_widget_context(widget_registry),
            **kwargs  # Additional context from caller
        }

        if top_title:
            context['top_title'] = top_title if top_title else 'FastPluggy'

        # Process all widgets (including any that were injected above)
        rendered_widgets = []
        for widget in widgets:
            try:
                rendered_widgets.append(self._process_widget(widget, request, **kwargs))
            except Exception as e:
                logger.exception("Failed to process widget {}: {}", widget, e)
                rendered_widgets.append(self._generate_error_widget(widget, e))

        context["widget_views"] = rendered_widgets

        # Pre-process topbar modal widgets with the page's widget list as DI context.
        # This lets widgets like DebugPanelWidget receive the page widgets as `items`.
        topbar_modal_widgets = {}
        for action in GlobalRegistry.get_global('topbar_actions', []):
            if action.modal_widgets:
                topbar_modal_widgets[action.id] = self.build_widget_entries(
                    action.modal_widgets, request, context_widgets=widgets
                )
        context["topbar_modal_widgets"] = topbar_modal_widgets

        return self.templates.TemplateResponse("generic_page.html.j2", context)

    @staticmethod
    def inject_global_widgets(widgets: list, request: Request, tag: str = '#ROOT#') -> list:
        """Inject globally-registered widgets for *tag* into the *widgets* list.

        Instantiates each entry via DI, passing the current widget list as
        ``context_widgets`` so widgets that declare
        ``items: Annotated[List[AbstractWidget], InjectDependency]``
        (e.g. ``DebugPanelWidget``) receive the page's existing widgets.

        Processing (setting ``request``, calling ``process()``) is intentionally
        deferred — ``ViewBuilder.generate()`` handles it in a single loop over
        all widgets after injection.
        """
        logger.debug(f"Injecting global widgets for tag '{tag}'")
        widget_injections = GlobalRegistry.get_global('list_widget_to_inject', default=[])
        entries = sorted(
            [e for e in widget_injections if e.get('tag') == tag],
            key=lambda x: x.get('position', 0),
        )
        for entry in entries:
            try:
                instance = ViewBuilder.instantiate_entry(entry, context_widgets=widgets)
                if instance is None:
                    continue
                position = entry.get('position')
                if position is not None:
                    widgets.insert(position, instance)
                else:
                    widgets.append(instance)
            except Exception as e:
                logger.exception(f"Failed to inject widget {entry}: {e}")
        return widgets

    @staticmethod
    def instantiate_entry(entry: dict, context_widgets: list) -> Optional[Any]:
        """Instantiate a single widget entry dict via DI.

        Returns ``None`` if the widget reports itself as not visible,
        or an error-dict if instantiation fails.
        """
        try:
            instance = call_with_injection(
                entry['widget'],
                user_kwargs=entry.get('kwargs', {}),
                context_dict={List['AbstractWidget']: context_widgets},
            )
        except Exception as e:
            logger.exception("Failed to instantiate widget {}: {}", entry.get('widget'), e)
            return ViewBuilder._generate_error_widget(entry.get('widget'), e)
        if hasattr(instance, 'is_visible') and not instance.is_visible():
            return None
        return instance

    @staticmethod
    def build_widget_entries(entries: list, request: Request, context_widgets: list = None) -> list:
        """Instantiate and process a list of widget entry dicts.

        Each entry is ``{'widget': WidgetClass, 'kwargs': {...}}``.
        Used for standalone widget lists (e.g. topbar modal widgets) where
        processing cannot be deferred to a ``generate()`` loop.

        ``context_widgets`` is passed to the DI system so that widgets that
        declare ``items: Annotated[List[AbstractWidget], InjectDependency]``
        (e.g. ``DebugPanelWidget``) receive the caller's widget list.
        """
        if context_widgets is None:
            context_widgets = []
        result = []
        for entry in entries:
            instance = ViewBuilder.instantiate_entry(entry, context_widgets=context_widgets)
            if instance is None:
                continue
            # Error dicts (from failed instantiation) are passed through as-is
            if isinstance(instance, dict):
                result.append(instance)
                continue
            ViewBuilder._process_widget(instance, request)
            result.append(instance)
        return result

    @staticmethod
    def _process_widget(widget: Any, request: Request, **kwargs) -> Any:
        """Process an individual widget/component."""
        if hasattr(widget, "request"):
            widget.request = request
        try:
            from fastpluggy.core.database import get_db
            if hasattr(widget, "db"):
                widget.db = next(get_db())
        except (ImportError, Exception):
            pass
        if hasattr(widget, "process"):
            widget.process(request=request, **kwargs)
        return widget

    @staticmethod
    def _build_widget_context(widget_registry: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
        """Build template context from widget registry."""
        widget_templates = {}
        widget_macros = {}

        for widget_type, widget_info in widget_registry.items():
            try:
                template_name = widget_info.get('template_name')
                macro_name = widget_info.get('macro_name')

                if template_name:
                    widget_templates[widget_type] = template_name
                elif macro_name:
                    widget_macros[widget_type] = macro_name
            except Exception as e:
                logger.exception(f"Failed to build widget context for {widget_type}: {e}")

        return {
            'widget_templates': widget_templates,
            'widget_macros': widget_macros,
            'registered_widget_types': list(widget_registry.keys()),
            'widget_registry': widget_registry,
        }

    @staticmethod
    def _generate_error_widget(widget, error):
        """Generate error widget for failed widgets."""
        return {
            "widget_type": "error",
            "type": "error",
            "title": f"Error in {type(widget).__name__}",
            "error_message": str(error),
            "traceback_str": ''.join(traceback.format_exception(type(error), error, error.__traceback__))
        }
