from typing import Callable, Union
from typing import Type, Dict, Any, List, Optional

from pydantic import BaseModel
from sqlalchemy.orm import Session, InstrumentedAttribute

from fastpluggy.core.models_tools.pydantic import ModelToolsPydantic
from fastpluggy.core.models_tools.shared import ModelToolsShared
from fastpluggy.core.models_tools.sqlalchemy import ModelToolsSQLAlchemy
from fastpluggy.core.widgets.base import AbstractWidget
from fastpluggy.core.widgets.mixins import FieldHandlingMixin


class ModelWidget(AbstractWidget, FieldHandlingMixin):
    """
    A component to render a detailed view of a SQLAlchemy or Pydantic model instance.
    """
    widget_type = "model_view"

    template_name = "widgets/data/view_model.html.j2"
    macro_name = "render_model_view"
    render_method = "macro"

    def __init__(
            self,
            model: Union[BaseModel, Type[BaseModel], Any, Type[Any]],
            filters: Optional[Union[Callable[[Any], Any], Dict[str, Any]]] = None,
            fields: Optional[List[str]] = None,
            exclude_fields: Optional[List[str | InstrumentedAttribute]] = None,
            field_callbacks: Optional[Dict[str | InstrumentedAttribute, Callable[[Any], Any]]] = None,
            readonly_fields: Optional[List[str]] = None,
            title: Optional[str] = None,
            db: Optional[Session] = None,
            **kwargs
    ):
        super().__init__(**kwargs)

        self.model = model
        self.filters = filters
        self.fields = fields
        self.exclude_fields = self.process_fields_names(exclude_fields or [])
        self.field_callbacks = self.process_field_callbacks(field_callbacks or {})
        self.readonly_fields = readonly_fields or []
        self.title = title
        self.params = kwargs
        self.db = db
        self.data = {}

    def process(self, **kwargs) -> None:
        instance = None
        if self.model:
            if ModelToolsPydantic.is_model_instance(self.model):
                instance = self.model
            elif ModelToolsPydantic.is_model_class(self.model):
                instance = self.model(**(self.filters or {}))
            elif ModelToolsPydantic.is_settings_instance(self.model):
                instance = self.model
            elif ModelToolsPydantic.is_settings_class(self.model):
                instance = self.model(**(self.filters or {}))
            elif ModelToolsSQLAlchemy.is_sqlalchemy_model_instance(self.model):
                instance = self.model
            elif ModelToolsSQLAlchemy.is_sqlalchemy(self.model):
                if not self.db:
                    raise ValueError("Database session is required to query the SQLAlchemy model.")
                query = ModelToolsSQLAlchemy.create_filtered_query(
                    db=self.db, model=self.model, filters=self.filters
                )
                instance = query.first()
                if not instance:
                    raise ValueError("No data found for the given filters.")
            elif type(self.model) is dict:
                instance = self.model

            self.data = ModelToolsShared.extract_model_data(
                instance, fields=self.fields, exclude=self.exclude_fields
            )

        for field, callback in self.field_callbacks.items():
            if field in self.data:
                self.data[field] = callback(self.data[field])
