"""
Base class for Gradio apps.

Each app in the apps/ directory should inherit from this base class
and implement the create_interface() method.
"""

from abc import ABC, abstractmethod
from typing import Generic, Optional, Type, TypeVar

import gradio as gr
from pydantic import BaseModel

import config
from utils.logger import get_logger

# TypeVar for generic config type
ConfigT = TypeVar("ConfigT", bound=BaseModel)


class BaseApp(ABC, Generic[ConfigT]):
    """
    Base class for all Gradio apps.

    Each app should inherit from this class and implement:
    - create_interface(): Returns a Gradio interface/blocks

    Type parameter ConfigT can be specified for type-safe configuration access:
        class MyAppConfig(BaseModel):
            api_endpoint: str

        class MyApp(BaseApp[MyAppConfig]):
            ConfigModel = MyAppConfig

            def create_interface(self):
                # self.config is now typed as MyAppConfig
                endpoint = self.config.api_endpoint  # Full type hints!

    App metadata (title, icon, description) is loaded from config.yaml
    based on the app's filename (without .py extension).

    Provides centralized logging:
    - Console: Rich formatted logging (via utils.logger)
    - Web UI: Gradio Info/Warning/Error components
    """

    def __init__(self, app_key: str):
        """
        Initialize the app.

        Args:
            settings: Application settings instance
            app_key: The app key (filename without .py) used to lookup config
        """
        self.settings = config.settings
        self.app_key = app_key
        self._app_config = None
        self._parsed_config = None

        # Get logger from centralized logging system
        # Use project name from settings if available, otherwise use class name
        project_prefix = (
            self.settings.project_name.lower().replace(" ", "_").replace("-", "_")
        )
        logger_name = f"{project_prefix}.{self.app_key or self.__class__.__name__}"
        self.logger = get_logger(logger_name)

        # Load app config from settings if available
        self._app_config = self.settings.get_app_config(app_key=app_key)

        # Parse app-specific config if available and ConfigModel is defined
        if self._app_config and self._app_config.config and self.ConfigModel:
            try:
                self._parsed_config = self.ConfigModel(**self._app_config.config)
            except Exception as e:
                self.logger.error(f"Failed to parse config for {app_key}: {e}")

    def log_info(self, message: str, show_ui: bool = False) -> Optional[str]:
        """
        Log an info message to console and optionally to UI.

        Args:
            message: The message to log
            show_ui: If True, also display in Gradio UI

        Returns:
            Message string if show_ui=True, None otherwise
        """
        self.logger.info(f"[cyan]{message}[/cyan]")
        if show_ui:
            return message
        return None

    def log_warning(self, message: str, show_ui: bool = False) -> Optional[str]:
        """
        Log a warning message to console and optionally to UI.

        Args:
            message: The warning message to log
            show_ui: If True, also display in Gradio UI

        Returns:
            Message string if show_ui=True, None otherwise
        """
        self.logger.warning(f"[yellow]{message}[/yellow]")
        if show_ui:
            return message
        return None

    def log_error(
        self, message: str, show_ui: bool = False, exc_info: bool = False
    ) -> Optional[str]:
        """
        Log an error message to console and optionally to UI.

        Args:
            message: The error message to log
            show_ui: If True, also display in Gradio UI
            exc_info: If True, include exception traceback

        Returns:
            Message string if show_ui=True, None otherwise
        """
        self.logger.error(f"[red]{message}[/red]", exc_info=exc_info)
        if show_ui:
            return message
        return None

    def log_debug(self, message: str) -> None:
        """
        Log a debug message to console.

        Args:
            message: The debug message to log
        """
        self.logger.debug(f"[dim]{message}[/dim]")

    @property
    def name(self) -> str:
        """
        Return the display name of the app (used for tab label).

        First checks config.yaml for app title, falls back to app_key or class name.
        """
        if self._app_config:
            return f"{self._app_config.icon} {self._app_config.title}"
        return self.app_key or self.__class__.__name__

    @property
    def title(self) -> str:
        """Return the title without icon."""
        if self._app_config:
            return self._app_config.title
        return self.app_key or self.__class__.__name__

    @property
    def icon(self) -> str:
        """Return the emoji icon for the app."""
        if self._app_config:
            return self._app_config.icon
        return "📱"

    @property
    def description(self) -> Optional[str]:
        """
        Return a description of the app.

        First checks config.yaml, then falls back to class docstring.
        """
        if self._app_config and self._app_config.description:
            return self._app_config.description

        # Fall back to class docstring
        if self.__doc__:
            return self.__doc__.strip().split("\n")[0]

        return None

    @property
    def order(self) -> int:
        """Return the display order for this app."""
        if self._app_config:
            return self._app_config.order
        return 100

    @property
    def enabled(self) -> bool:
        """Return whether this app is enabled."""
        if self._app_config:
            return self._app_config.enabled
        return True

    # Class attribute for configuration model (override in subclass)
    ConfigModel: Optional[Type[ConfigT]] = None

    @property
    def config(self) -> Optional[ConfigT]:
        """
        Return the parsed app-specific configuration instance.

        Type-safe: Returns ConfigT if specified in class definition, else BaseModel.

        Returns None if:
        - No config is defined in config.yaml
        - ConfigModel is not defined
        - Config parsing failed

        Example with type hints:
            class MyAppConfig(BaseModel):
                api_endpoint: str

            class MyApp(BaseApp[MyAppConfig]):
                ConfigModel = MyAppConfig

                def create_interface(self):
                    # self.config is typed as MyAppConfig
                    endpoint = self.config.api_endpoint  # Auto-complete works!

        Returns:
            ConfigT instance or None: Parsed configuration with proper type hints
        """
        return self._parsed_config

    @abstractmethod
    def create_interface(self) -> gr.Blocks:
        """
        Create and return the Gradio interface for this app.

        Returns:
            gr.Blocks: Gradio Blocks interface
        """
        pass
