"""AppHandler — thin coordinator facade over AppLifecycleService.

Owns the AppRegistry and delegates all lifecycle operations to
AppLifecycleService (a Resource child).
"""

import typing
from logging import getLogger

from hassette.core.app_change_detector import ChangeSet
from hassette.core.app_lifecycle_service import AppLifecycleService
from hassette.core.app_registry import AppRegistry, AppStatusSnapshot
from hassette.resources.base import Resource
from hassette.types import Topic
from hassette.types.types import LOG_LEVEL_TYPE

if typing.TYPE_CHECKING:
    from hassette import AppConfig, Hassette
    from hassette.app.app import App

LOGGER = getLogger(__name__)


class AppHandler(Resource):
    """Manages the lifecycle of apps in Hassette.

    Acts as a thin facade coordinating:
    - AppRegistry: State tracking and queries
    - AppLifecycleService: Lifecycle orchestration, change detection, factory
    """

    # TODO: handle stopping/starting individual app instances, instead of all apps of a class/key
    # no need to restart app index 2 if only app index 0 changed, etc.

    registry: AppRegistry
    """Registry for tracking app state."""

    lifecycle: AppLifecycleService
    """Service owning lifecycle orchestration, change detection, and factory."""

    def __init__(self, hassette: "Hassette", *, parent: Resource | None = None) -> None:
        super().__init__(hassette, parent=parent)

        self.registry = AppRegistry()
        self.lifecycle = self.add_child(AppLifecycleService, registry=self.registry)
        self.lifecycle.set_apps_configs(hassette.config.app_manifests)

    # --- Public API (thin delegation) ---

    @property
    def apps(self) -> dict[str, dict[int, "App[AppConfig]"]]:
        """Running apps - delegates to registry."""
        return self.registry.apps

    def get_status_snapshot(self) -> AppStatusSnapshot:
        """Get immutable snapshot of all app states for web UI."""
        return self.registry.get_snapshot()

    @property
    def config_log_level(self) -> LOG_LEVEL_TYPE:
        """Return the log level from the config for this resource."""
        return self.hassette.config.app_handler_log_level

    def get(self, app_key: str, index: int = 0) -> "App[AppConfig] | None":
        """Get a specific app instance if running."""
        return self.registry.get(app_key, index)

    def all(self) -> list["App[AppConfig]"]:
        """All running app instances."""
        return self.registry.all_apps()

    # --- Lifecycle hooks ---

    async def on_initialize(self) -> None:
        """Set up file-watcher subscription, wait for websocket, and signal readiness."""
        if self.hassette.config.dev_mode or self.hassette.config.allow_reload_in_prod:
            if self.hassette.config.allow_reload_in_prod:
                self.logger.warning("Allowing app reloads in production mode due to config")
            self.logger.debug("Watching for app changes...")
            self.lifecycle.bus.on(topic=Topic.HASSETTE_EVENT_FILE_WATCHER, handler=self.lifecycle.handle_change_event)
        else:
            self.logger.debug("Not watching for app changes, dev_mode is disabled")

        await self.hassette.wait_for_ready(self.hassette._websocket_service)
        self.mark_ready(reason="initialized")

    async def after_initialize(self) -> None:
        """Wait for all Hassette services, then spawn app bootstrap.

        bootstrap_apps runs in AppHandler's task_bucket; individual app
        initializations are spawned into AppLifecycleService's task_bucket
        (child Resource, shut down before parent).
        """
        if not await self.hassette.wait_for_ready(
            [
                self.hassette._websocket_service,
                self.hassette._api_service,
                self.hassette._bus_service,
                self.hassette._scheduler_service,
                self.hassette._state_proxy,
            ]
        ):
            self.logger.warning("Dependencies never became ready; skipping app startup")
            return

        self.logger.debug("Scheduling app initialization")
        self.task_bucket.spawn(self.lifecycle.bootstrap_apps())

    async def on_shutdown(self) -> None:
        """Shutdown all app instances gracefully."""
        self.logger.debug("Stopping '%s' %s", self.class_name, self.role)
        self.mark_not_ready(reason="shutting-down")
        await self.lifecycle.shutdown_all()

    # --- Delegated operations ---

    async def start_app(self, app_key: str, force_reload: bool = False) -> None:
        """Start an app by key — delegates to lifecycle service."""
        await self.lifecycle.start_app(app_key, force_reload=force_reload)

    async def stop_app(self, app_key: str) -> None:
        """Stop an app by key — delegates to lifecycle service."""
        await self.lifecycle.stop_app(app_key)

    async def reload_app(self, app_key: str, force_reload: bool = False) -> None:
        """Reload an app by key — delegates to lifecycle service."""
        await self.lifecycle.reload_app(app_key, force_reload=force_reload)

    async def apply_changes(self, changes: ChangeSet) -> None:
        """Apply detected changes — delegates to lifecycle service."""
        await self.lifecycle.apply_changes(changes)
