from __future__ import annotations

import asyncio
import json
from importlib import import_module
from pathlib import Path
from typing import Any

from openclaw_lite.config import AppConfig, ProviderConfig, ensure_directories, load_config, save_config
from openclaw_lite.core.agent import LiteAgent
from openclaw_lite.core.registry import ProviderRegistry, ToolRegistry
from openclaw_lite.core.sessions import SessionStore
from openclaw_lite.schema import ChatContext, ToolResult
from openclaw_lite.tools import filesystem, http_tools, shell, system_tools
from openclaw_lite.utils import utc_now_iso


class Runtime:
    def __init__(self, config: AppConfig) -> None:
        self.config = config
        ensure_directories(config)
        self.tool_registry = ToolRegistry()
        self.provider_registry = ProviderRegistry()
        self.sessions = SessionStore(
            base_dir=config.paths.sessions,
            default_provider=config.default_provider_name,
            default_model=config.default_model_for(config.default_provider_name),
            default_mode=config.shell.default_mode,
        )
        self.agent = LiteAgent(self)
        self._skill_manager = None
        self._extensions_loaded = False
        self._last_skills_check = 0.0
        self._lock = asyncio.Lock()

    async def initialize(self) -> None:
        self._register_builtin_tools()
        self._load_providers()
        await self.reload_extensions()

    def _register_builtin_tools(self) -> None:
        self.tool_registry.register_many(shell.build_tools())
        self.tool_registry.register_many(filesystem.build_tools())
        self.tool_registry.register_many(http_tools.build_tools())
        self.tool_registry.register_many(system_tools.build_tools())

    def _load_providers(self) -> None:
        self.provider_registry = ProviderRegistry()
        factory_module = import_module("openclaw_lite.providers.factory")
        factory = getattr(factory_module, "build_provider")
        for name, provider_config in self.config.providers.items():
            provider = factory(provider_config)
            self.provider_registry.register(name, provider)

    async def reload_extensions(self) -> dict[str, Any]:
        async with self._lock:
            summary: dict[str, Any] = {"skills": None, "telegram_tools": None}
            try:
                skills_module = import_module("openclaw_lite.skills.loader")
                manager_class = getattr(skills_module, "SkillManager")
                self._skill_manager = manager_class(self.config.paths.skills)
                reload_result = await self._skill_manager.reload()
                self._sync_skill_tools()
                summary["skills"] = reload_result
            except ModuleNotFoundError:
                summary["skills"] = {"loaded": [], "errors": ["skills module not ready"]}

            try:
                telegram_module = import_module("openclaw_lite.tools.telegram_tools")
                builder = getattr(telegram_module, "build_tools")
                self._replace_tool_prefix("telegram_")
                self.tool_registry.register_many(builder())
                summary["telegram_tools"] = {"loaded": [tool.name for tool in builder()]}
            except ModuleNotFoundError:
                summary["telegram_tools"] = {"loaded": [], "errors": ["telegram tools module not ready"]}

            self._extensions_loaded = True
            return summary

    async def reload_extensions_if_needed(self) -> None:
        if not self._skill_manager:
            return
        changed = await self._skill_manager.reload_if_changed()
        if changed:
            self._sync_skill_tools()

    def _replace_tool_prefix(self, prefix: str) -> None:
        survivors = [tool for tool in self.tool_registry.list() if not tool.name.startswith(prefix)]
        self.tool_registry = ToolRegistry()
        self.tool_registry.register_many(survivors)

    def _sync_skill_tools(self) -> None:
        if not self._skill_manager:
            return
        self._replace_tool_prefix("skill_")
        for tool in self._skill_manager.tools():
            self.tool_registry.register(tool)

    def skill_prompt(self) -> str:
        if not self._skill_manager:
            return ""
        return self._skill_manager.prompt_text()

    def reload_config_from_mapping(self, mapping: dict[str, Any]) -> AppConfig:
        temp_path = self.config.paths.config_file
        temp_path.write_text(json.dumps(mapping, ensure_ascii=False), encoding="utf-8")
        updated = load_config(temp_path)
        self.config = updated
        ensure_directories(self.config)
        self._load_providers()
        return self.config

    def add_provider(
        self,
        *,
        name: str,
        provider_type: str,
        api_key: str,
        base_url: str | None,
        default_model: str | None,
    ) -> None:
        self.config.providers[name] = ProviderConfig(
            name=name,
            type=provider_type,
            api_key=api_key,
            base_url=base_url,
            default_model=default_model,
        )
        self._load_providers()

    async def chat(self, session_id: str, text: str, chat_context: ChatContext | None = None) -> str:
        return await self.agent.respond(session_id=session_id, user_text=text, chat=chat_context)

    async def list_models(self, provider_name: str | None = None) -> dict[str, list[str]]:
        targets = [provider_name] if provider_name else self.provider_registry.list_names()
        result: dict[str, list[str]] = {}
        for name in targets:
            provider = self.provider_registry.get_optional(name)
            if provider is None:
                result[name] = []
                continue
            try:
                result[name] = await provider.list_models()
            except Exception as exc:  # pragma: no cover - defensive guard
                result[name] = [f"<error: {exc}>"]
        return result

    def audit_tool_call(self, session_id: str, tool_name: str, arguments: dict[str, Any], result: ToolResult) -> None:
        entry = {
            "ts": utc_now_iso(),
            "session_id": session_id,
            "tool": tool_name,
            "arguments": arguments,
            "ok": result.ok,
            "error": result.error,
        }
        log_path = self.config.paths.logs / "tool_calls.jsonl"
        log_path.parent.mkdir(parents=True, exist_ok=True)
        with log_path.open("a", encoding="utf-8") as file_obj:
            file_obj.write(json.dumps(entry, ensure_ascii=False) + "\n")


async def build_runtime(config_path: str | Path) -> Runtime:
    config = load_config(config_path)
    runtime = Runtime(config)
    await runtime.initialize()
    return runtime
