"""
Server App - 服务启动入口

职责：
- 配置加载
- 服务初始化
- 路由注册
- 服务启动
"""

import uuid
import socket
import asyncio
from typing import Optional, Dict, Any, List

from fastapi import FastAPI, WebSocket, Request
from fastapi.middleware.cors import CORSMiddleware
import uvicorn

from core.logger import log_event, set_log_side
from core.config import load_config, load_mcp_config, load_settings
from core.mcp_manager import MCPManager
from tools.base import BaseTool

from .constants import ServerConfig
from .session import ConnectionSessionManager
from .handler import WebSocketHandler


class ServerApp:
    """
    服务应用

    负责：
    1. 加载配置
    2. 初始化组件
    3. 注册路由
    4. 启动服务
    """

    def __init__(self, config_path: Optional[str] = None):
        """
        初始化服务应用

        Args:
            config_path: 配置文件路径（可选）
        """
        set_log_side("SERVER")
        # 如果没有指定配置路径，使用默认的 config.json
        if config_path:
            self.config = load_config(config_path)
        else:
            self.config = load_config()  # 使用默认值 "config.json"

        # 加载 MCP 配置
        self.mcp_config = load_mcp_config()
        self.mcp_manager: Optional[MCPManager] = None
        self.mcp_tools: List[BaseTool] = []

        # 创建 FastAPI 应用
        self.app = FastAPI(title="Agent WebSocket Server")
        self._setup_middleware()

        # 配置全局 ProviderManager（内置模型 + config 追加 + provider 默认值）
        from provider.provider_manager import ProviderManager
        ProviderManager.get().configure_from_app_config(self.config)

        # 初始化组件
        self._init_components()

        # 注册路由
        self._register_routes()

        # 注册启动事件（MCP 后台初始化，不阻塞服务启动）
        @self.app.on_event("startup")
        async def startup_event():
            async def _run_init():
                await self._init_mcp()
                log_event(
                    "mcp_init_task_finished",
                    tool_count=len(getattr(self, "mcp_tools", []) or []),
                )

            asyncio.create_task(_run_init())

        # 注册关闭事件（用于清理 MCP）
        @self.app.on_event("shutdown")
        async def shutdown_event():
            await self._cleanup_mcp()

    def _setup_middleware(self):
        """设置中间件"""
        self.app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )

    def _init_components(self):
        """初始化组件"""
        # 确定默认模型：setting.json > config.provider.default
        model_name = self._get_default_model()

        log_event(
            "default_model_resolved",
            model_name=model_name,
        )

        # 创建会话管理器
        self.session_manager = ConnectionSessionManager()

        # 创建 MCP 工具获取函数（读取 self.mcp_tools 的最新值）
        mcp_tools_getter = lambda: self.mcp_tools

        # 创建 WebSocket 处理器（传递 mcp_tools_getter）
        self.ws_handler = WebSocketHandler(
            session_manager=self.session_manager,
            model_name=model_name,
            config=self.config,
            mcp_tools_getter=mcp_tools_getter,
        )

        log_event("components_initialized")

    def _is_model_available(self, model_fqn: str) -> bool:
        if not model_fqn or "/" not in model_fqn:
            return False
        from provider.provider_manager import ProviderManager

        pm = ProviderManager.get()
        try:
            pm.ensure_configured()
        except Exception:
            # ProviderManager is configured during ServerApp init; if not yet, treat as unavailable.
            return False

        return pm.get_model_info(model_fqn) is not None

    def _get_default_model(self) -> str:
        """
        获取默认模型名称

        优先级：setting.json > config.provider.default
        """
        # 检查 setting.json 中是否有持久化的模型选择
        settings = load_settings()
        saved_model = settings.get("default_model")
        if saved_model and self._is_model_available(saved_model):
            return saved_model

        # 使用配置中的默认模型
        default = self.config.provider.default
        if default and self._is_model_available(default):
            return default

        # 兜底：使用 ProviderManager 中的第一个模型
        from provider.provider_manager import ProviderManager

        pm = ProviderManager.get()
        pm.ensure_configured()
        models = pm.list_models()
        if models:
            first = models[0]
            return f"{first['provider']}/{first['name']}"

        raise ValueError("配置中没有定义任何模型")

    async def _init_mcp(self):
        """初始化 MCP 连接"""
        if self.mcp_config.mcp_servers:
            log_event("mcp_init_start", server_count=len(self.mcp_config.mcp_servers))
            try:
                def _on_tools_updated(tools: List[BaseTool]):
                    # MCP tools 增量更新：任何一个 server discover 完 tools 都会触发
                    self.mcp_tools = tools
                    try:
                        tool_names = [t.name for t in tools]
                    except Exception:
                        tool_names = []
                    log_event(
                        "mcp_tools_updated",
                        tool_count=len(tools),
                        tool_names=tool_names[:20],
                    )

                self.mcp_manager = MCPManager(self.mcp_config, on_tools_updated=_on_tools_updated)
                await self.mcp_manager.connect_all()
                # connect_all 返回时，mcp_tools 应该已被增量回调更新；这里再兜底同步一次
                self.mcp_tools = self.mcp_manager.get_tools()
                log_event("mcp_init_complete", tool_count=len(self.mcp_tools))

            except Exception as e:
                log_event("mcp_init_error", error=str(e))

    async def _cleanup_mcp(self):
        """清理 MCP 连接"""
        if self.mcp_manager:
            try:
                await self.mcp_manager.cleanup()
                log_event("mcp_cleanup_complete")
            except Exception as e:
                log_event("mcp_cleanup_error", error=str(e))

    def _register_routes(self):
        """注册路由"""

        @self.app.get("/")
        async def root():
            """健康检查"""
            return {
                "status": "ok",
                "service": "Agent WebSocket Server",
                "sessions": self.session_manager.session_count,
                "connections": self.ws_handler.get_active_connections(),
            }

        @self.app.get("/health")
        async def health():
            """健康检查端点"""
            return {"status": "healthy"}

        @self.app.get("/context/{session_id}")
        async def get_context(session_id: str):
            """获取会话完整信息（上下文 + 会话状态）"""
            session = self.session_manager.get_session(session_id)
            if not session:
                return {"token_count": 0, "max_tokens": 0, "percent": 0.0, "threshold": 80}
            return session.get_session_info()

        @self.app.post("/clear/{session_id}")
        async def clear_session(session_id: str):
            """清除会话上下文（重新开始会话）"""
            session = self.session_manager.get_session(session_id)
            if not session:
                return {"success": False, "message": "会话不存在"}

            # 清除会话历史
            session.clear_history()
            log_event("session_cleared", session_id=session_id)

            return {"success": True, "message": "会话已清除"}

        @self.app.post("/compact/{session_id}")
        async def compact_session(session_id: str):
            """压缩会话上下文（只有占比超过60%才执行）"""
            session = self.session_manager.get_session(session_id)
            if not session:
                return {"success": False, "message": "会话不存在"}

            # 获取上下文信息检查占比
            context_info = session.get_context_info(session_id)
            percent = context_info.get("percent", 0)

            if percent < 40:
                return {
                    "success": True,
                    "message": f"上下文占比 {percent:.1f}% 未达到 40%，无需压缩",
                    "skipped": True,
                    "percent": percent,
                }

            # 执行压缩
            try:
                result = await session.compact_session()
                log_event("session_compacted", session_id=session_id, percent=percent)
                return {
                    "success": True,
                    "message": f"压缩完成（压缩前占比 {percent:.1f}%）",
                    "compressed_count": result.get("compressed_count", 0),
                }
            except Exception as e:
                log_event("session_compact_error", session_id=session_id, error=str(e))
                return {"success": False, "message": f"压缩失败: {str(e)}"}

        @self.app.post("/integrations/rocketchat/outgoing")
        async def rocketchat_outgoing(request: Request):
            """Rocket.Chat Outgoing Webhook receiver.

            Configure Rocket.Chat integration:
            - Type: Outgoing Webhook
            - URLs: https://<this-server>/integrations/rocketchat/outgoing
            - Token: set to ROCKETCHAT_WEBHOOK_TOKEN (recommended)

            Env vars:
            - ROCKETCHAT_BASE_URL
            - ROCKETCHAT_AUTH_TOKEN
            - ROCKETCHAT_USER_ID
            - ROCKETCHAT_WEBHOOK_TOKEN (optional but recommended)
            """
            from integrations.rocketchat import (
                RocketChatConfig,
                RocketChatClient,
                StreamingTextAggregator,
                map_room_to_session_id,
                verify_outgoing_webhook,
            )
            from server.constants import EventType

            payload = await request.json()
            cfg = RocketChatConfig.from_env()

            if not verify_outgoing_webhook(payload, cfg):
                return {"success": False, "message": "invalid token"}

            room_id = payload.get("channel_id") or payload.get("roomId") or ""
            text = (payload.get("text") or "").strip()

            # Ignore empty messages (e.g. edits/joins depending on RC settings)
            if not room_id or not text:
                return {"success": True, "skipped": True}

            session_id = map_room_to_session_id(room_id)

            session = self.session_manager.get_session(session_id)
            sender = RocketChatClient(cfg)
            aggregator = StreamingTextAggregator()

            async def event_callback(event: Dict[str, Any]):
                # Mirror WS event types but push text to Rocket.Chat.
                etype = event.get("type")
                if hasattr(etype, "value"):
                    etype = etype.value

                data = event.get("data") or {}

                if etype in {EventType.TEXT_CHUNK.value, "text_chunk"}:
                    chunk = data.get("text", "") or ""
                    flushed = aggregator.add(chunk)
                    if flushed:
                        await sender.post_message(room_id=room_id, text=flushed)
                    return

                if etype in {EventType.TASK_COMPLETED.value, "task_completed"}:
                    flushed = aggregator.flush()
                    if flushed:
                        await sender.post_message(room_id=room_id, text=flushed)
                    msg = data.get("message") or "(task completed)"
                    await sender.post_message(room_id=room_id, text=msg)
                    return

                if etype in {EventType.ERROR.value, "error"}:
                    flushed = aggregator.flush()
                    if flushed:
                        await sender.post_message(room_id=room_id, text=flushed)
                    err = data.get("message") or "unknown error"
                    await sender.post_message(room_id=room_id, text=f"[error] {err}")
                    return

            if not session:
                # New room session: create and init with build agent by default.
                session = self.session_manager.create_session(
                    session_id=session_id,
                    model_name=self._get_default_model(),
                    config=self.config,
                    mcp_tools_getter=lambda: self.mcp_tools,
                )
                from server.constants import AgentType

                await session.initialize(
                    agent_type=AgentType.BUILD,
                    event_callback=event_callback,
                )
            else:
                session._event_callback = event_callback
                session._is_connected = True

            # Execute user message through existing pipeline.
            await session.execute(text)

            # Flush remaining buffered stream.
            flushed = aggregator.flush()
            if flushed:
                await sender.post_message(room_id=room_id, text=flushed)

            return {"success": True, "session_id": session_id}

        @self.app.get("/sessions")
        async def list_sessions(limit: int = 20):
            """获取会话列表（文件 I/O 放到线程池，避免阻塞事件循环）"""
            import asyncio
            from server.session_manager import SessionManager

            sm = SessionManager(self.config)
            loop = asyncio.get_event_loop()
            sessions = await loop.run_in_executor(None, sm.list_sessions, limit)
            return {"sessions": sessions, "total": len(sessions)}

        @self.app.get("/models")
        async def get_models():
            """获取可用模型列表（内置 + 配置追加，合并去重）"""
            from provider.provider_manager import ProviderManager

            pm = ProviderManager.get()
            return {"models": pm.list_models()}

        @self.app.websocket("/ws/{session_id}")
        async def websocket_endpoint(websocket: WebSocket, session_id: str):
            """WebSocket 端点（指定会话 ID，用于恢复历史会话）"""
            agent_type = websocket.query_params.get("agent_type", "general")
            await self.ws_handler.handle_connection(websocket, session_id, agent_type)

        @self.app.websocket("/ws")
        async def websocket_endpoint_auto(websocket: WebSocket):
            """WebSocket 端点（自动生成会话 ID）"""
            session_id = str(uuid.uuid4())
            agent_type = websocket.query_params.get("agent_type", "general")
            await self.ws_handler.handle_connection(websocket, session_id, agent_type)

    def run(
        self,
        host: Optional[str] = None,
        port: Optional[int] = None,
    ):
        """
        启动服务

        Args:
            host: 主机地址
            port: 端口号
        """
        host = host or ServerConfig.DEFAULT_HOST
        port = port or ServerConfig.DEFAULT_PORT

        # 自动切换端口
        port = self._find_available_port(host, port)

        log_event(
            "server_starting",
            host=host,
            port=port,
        )

        uvicorn.run(
            self.app,
            host=host,
            port=port,
            ws_ping_interval=ServerConfig.WS_PING_INTERVAL,
            ws_ping_timeout=ServerConfig.WS_PING_TIMEOUT,
        )

    def _find_available_port(self, host: str, port: int) -> int:
        """
        查找可用端口

        Args:
            host: 主机地址
            port: 起始端口

        Returns:
            可用端口
        """
        for attempt in range(ServerConfig.MAX_PORT_ATTEMPTS):
            current_port = port + attempt
            if self._is_port_available(host, current_port):
                if attempt > 0:
                    log_event(
                        "port_switched",
                        original_port=port,
                        new_port=current_port,
                    )
                return current_port

        # 所有端口都被占用
        raise RuntimeError(
            f"无法找到可用端口（尝试了 {port} 到 {port + ServerConfig.MAX_PORT_ATTEMPTS - 1}）"
        )

    def _is_port_available(self, host: str, port: int) -> bool:
        """检查端口是否可用"""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.bind((host, port))
                return True
        except OSError:
            return False


def start_server(
    host: str = ServerConfig.DEFAULT_HOST,
    port: int = ServerConfig.DEFAULT_PORT,
    config_path: Optional[str] = None,
):
    """
    启动服务器

    Args:
        host: 主机地址
        port: 端口号
        config_path: 配置文件路径
    """
    server_app = ServerApp(config_path=config_path)
    server_app.run(host=host, port=port)


# 延迟初始化的 app 实例（用于 uvicorn 直接运行）
# 注意：直接运行时需要确保配置文件存在
_lazy_app: Optional[FastAPI] = None


def get_app() -> FastAPI:
    """
    获取 FastAPI 应用实例（延迟初始化）

    用于 uvicorn 直接运行：uvicorn src.server.app:get_app --factory
    """
    return ServerApp().app


if __name__ == "__main__":
    start_server()
