"""
sk_mini.py — a minimal Semantic-Kernel-style kernel + plugins (Knowledge Module
03 §7), so you can SEE the "kernel hosts plugins of functions" model that
Semantic Kernel pioneered and that the Microsoft Agent Framework carries forward.

Semantic Kernel's core idea: you write PLUGINS (classes) whose methods are
KERNEL FUNCTIONS (decorated with name + description = the schema the LLM reads).
The KERNEL registers plugins and invokes their functions — and an LLM planner can
pick which to call. This mini version captures that shape; the real SDK adds the
LLM planner, connectors (Azure OpenAI), memory, filters, and telemetry.
"""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Callable


def kernel_function(description: str, name: str | None = None):
    """Decorator marking a method as an LLM-callable kernel function (mirrors
    Semantic Kernel's @kernel_function). Stores the metadata the model reads."""
    def deco(fn: Callable):
        fn._sk_function = {"name": name or fn.__name__, "description": description}
        return fn
    return deco


@dataclass
class FunctionView:
    plugin: str
    name: str
    description: str
    invoke: Callable


class Kernel:
    """Hosts plugins and invokes their functions by (plugin, function) name."""

    def __init__(self) -> None:
        self._functions: dict[tuple[str, str], FunctionView] = {}

    def add_plugin(self, instance: object, plugin_name: str) -> None:
        for attr in dir(instance):
            fn = getattr(instance, attr)
            meta = getattr(fn, "_sk_function", None)
            if meta:
                self._functions[(plugin_name, meta["name"])] = FunctionView(
                    plugin_name, meta["name"], meta["description"], fn)

    def register_function(self, plugin: str, name: str, description: str,
                          invoke: Callable) -> None:
        """Register a single function directly (used to bridge an MCP tool into
        the kernel — see run.py). This is how MCP decouples tools from frameworks:
        a tool defined once on an MCP server becomes a kernel function here."""
        self._functions[(plugin, name)] = FunctionView(plugin, name, description, invoke)

    def functions(self) -> list[FunctionView]:
        """The catalog an LLM planner sees (name + description per function)."""
        return list(self._functions.values())

    def invoke(self, plugin: str, function: str, **kwargs):
        view = self._functions.get((plugin, function))
        if not view:
            raise KeyError(f"no function {plugin}.{function}")
        return view.invoke(**kwargs)


# --- an example banking plugin ----------------------------------------------
class BankingPlugin:
    """A Semantic-Kernel-style plugin. Each kernel_function is something an LLM
    planner could choose to call. Money facts still come from the system of
    record passed in — never invented."""

    def __init__(self, balances: dict[str, float]) -> None:
        self._balances = balances

    @kernel_function("Get the current account balance in AED for an account id.")
    def get_balance(self, account_id: str) -> str:
        bal = self._balances.get(account_id)
        return f"{bal:.2f} AED" if bal is not None else "account not found"

    @kernel_function("Return the bank's transaction dispute window in days.")
    def dispute_window_days(self) -> str:
        return "60"
