from __future__ import annotations
from server.chat import chat_bp
import json, time
from datetime import datetime
from typing import Dict, Any, Optional
from pathlib import Path
from io import BytesIO
import zipfile
import os

from flask import Blueprint, jsonify, request, session, send_file
from werkzeug.utils import secure_filename
from werkzeug.exceptions import RequestEntityTooLarge
import secrets

from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
from modules.personalization_manager import (
    load_personalization_config,
    resolve_context_compression_settings,
    save_personalization_config,
    THINKING_INTERVAL_MIN,
    THINKING_INTERVAL_MAX,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
)
from modules.skills_manager import (
    get_skills_catalog,
    infer_private_skills_dir,
    merge_enabled_skills,
    sync_workspace_skills,
)
from modules.upload_security import UploadSecurityError
from modules.host_sandbox_policy import load_policy, save_policy
from modules.user_manager import UserWorkspace
from core.web_terminal import WebTerminal
from config.model_profiles import get_model_context_window

from server.auth_helpers import api_login_required, resolve_admin_policy, get_current_user_record, get_current_username
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
from server.security import rate_limited, prune_socket_tokens
from server.utils_common import debug_log
from server.state import PROJECT_MAX_STORAGE_MB, THINKING_FAILURE_KEYWORDS, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
from server.state import tool_approval_manager, user_question_manager
from server.extensions import socketio
from server.monitor import get_cached_monitor_snapshot
from server.files import sanitize_filename_preserve_unicode

UPLOAD_FOLDER_NAME = ".astrion/user_upload"
def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: str, source: str = "notify") -> None:
    notice = str(text or "").strip()
    if not notice:
        return

    # 仅在“有运行中的任务”时注入 [系统通知|notify]，空闲期切换不写入 user 消息。
    try:
        from server.tasks import task_manager

        current_conv = getattr(getattr(terminal, "context_manager", None), "current_conversation_id", None)
        running_tasks = [
            r for r in task_manager.list_tasks(username) if r.status in {"pending", "running", "cancel_requested"}
        ]
        if current_conv:
            running_tasks.sort(key=lambda r: 0 if r.conversation_id == current_conv else 1)
        if not running_tasks:
            debug_log(f"[RuntimeMode] idle switch ignored notify injection: {notice}")
            return

        target = running_tasks[0]
        result = task_manager.enqueue_runtime_guidance(
            username=username,
            task_id=target.task_id,
            message=notice,
            source=source,
        )
        if not result.get("success"):
            debug_log(f"[RuntimeMode] enqueue runtime guidance failed: {result}")
    except Exception as exc:
        debug_log(f"[RuntimeMode] dispatch notice failed: {exc}")

@chat_bp.route('/api/thinking-mode', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("thinking_mode_toggle", 15, 60, scope="user")
def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, username: str):
    """切换思考模式"""
    try:
        data = request.get_json() or {}
        requested_mode = data.get('mode')
        if requested_mode in {"fast", "thinking", "deep"}:
            target_mode = requested_mode
        elif 'thinking_mode' in data:
            target_mode = "thinking" if bool(data.get('thinking_mode')) else "fast"
        else:
            target_mode = terminal.run_mode
        terminal.set_run_mode(target_mode)
        if terminal.thinking_mode:
            terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
        else:
            terminal.api_client.start_new_task()
        session['thinking_mode'] = terminal.thinking_mode
        session['run_mode'] = terminal.run_mode
        # 更新当前对话的元数据
        ctx = terminal.context_manager
        if ctx.current_conversation_id:
            try:
                ctx.conversation_manager.save_conversation(
                    conversation_id=ctx.current_conversation_id,
                    messages=ctx.conversation_history,
                    project_path=str(ctx.project_path),
                    todo_list=ctx.todo_list,
                    thinking_mode=terminal.thinking_mode,
                    run_mode=terminal.run_mode,
                    model_key=getattr(terminal, "model_key", None),
                    has_images=getattr(ctx, "has_images", False),
                    has_videos=getattr(ctx, "has_videos", False)
                )
            except Exception as exc:
                print(f"[API] 保存思考模式到对话失败: {exc}")
        
        status = terminal.get_status()
        socketio.emit('status_update', status, room=f"user_{username}")
        
        return jsonify({
            "success": True,
            "data": {
                "thinking_mode": terminal.thinking_mode,
                "mode": terminal.run_mode
            }
        })
    except Exception as exc:
        print(f"[API] 切换思考模式失败: {exc}")
        code = 400 if isinstance(exc, ValueError) else 500
        return jsonify({
            "success": False,
            "error": str(exc),
            "message": "切换思考模式时发生异常"
        }), code

@chat_bp.route('/api/model', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("model_switch", 10, 60, scope="user")
def update_model(terminal: WebTerminal, workspace: UserWorkspace, username: str):
    """切换基础模型（快速/思考模型组合）。"""
    try:
        data = request.get_json() or {}
        model_key = data.get("model_key")
        if not model_key:
            return jsonify({"success": False, "error": "缺少 model_key"}), 400

        # 管理员禁用模型校验
        policy = resolve_admin_policy(get_current_user_record())
        disabled_models = set(policy.get("disabled_models") or [])
        if model_key in disabled_models:
            return jsonify({
                "success": False,
                "error": "该模型已被管理员禁用",
                "message": "被管理员强制禁用"
            }), 403

        terminal.set_model(model_key)
        # fast-only 时 run_mode 可能被强制为 fast
        session["model_key"] = terminal.model_key
        session["run_mode"] = terminal.run_mode
        session["thinking_mode"] = terminal.thinking_mode

        # 更新当前对话元数据
        ctx = terminal.context_manager
        if ctx.current_conversation_id:
            try:
                ctx.conversation_manager.save_conversation(
                    conversation_id=ctx.current_conversation_id,
                    messages=ctx.conversation_history,
                    project_path=str(ctx.project_path),
                    todo_list=ctx.todo_list,
                    thinking_mode=terminal.thinking_mode,
                    run_mode=terminal.run_mode,
                    model_key=terminal.model_key,
                    has_images=getattr(ctx, "has_images", False),
                    has_videos=getattr(ctx, "has_videos", False)
                )
            except Exception as exc:
                print(f"[API] 保存模型到对话失败: {exc}")

        status = terminal.get_status()
        socketio.emit('status_update', status, room=f"user_{username}")

        return jsonify({
            "success": True,
            "data": {
                "model_key": terminal.model_key,
                "run_mode": terminal.run_mode,
                "thinking_mode": terminal.thinking_mode
            }
        })
    except Exception as exc:
        print(f"[API] 切换模型失败: {exc}")
        code = 400 if isinstance(exc, ValueError) else 500
        return jsonify({"success": False, "error": str(exc), "message": str(exc)}), code

@chat_bp.route('/api/personalization', methods=['GET'])
@api_login_required
@with_terminal
def get_personalization_settings(terminal: WebTerminal, workspace: UserWorkspace, username: str):
    """获取个性化配置"""
    try:
        policy = resolve_admin_policy(get_current_user_record())
        if policy.get("ui_blocks", {}).get("block_personal_space"):
            return jsonify({"success": False, "error": "个人空间已被管理员禁用"}), 403
        data = load_personalization_config(workspace.data_dir)
        private_skills_dir = infer_private_skills_dir(workspace.data_dir)
        skills_catalog = get_skills_catalog(private_dir=private_skills_dir)
        enabled_skills = merge_enabled_skills(
            data.get("enabled_skills"),
            skills_catalog,
            data.get("skills_catalog_snapshot"),
        )
        data_out = dict(data)
        data_out["enabled_skills"] = enabled_skills
        compression_settings = resolve_context_compression_settings(data_out)
        try:
            model_window = get_model_context_window(getattr(terminal, "model_key", None))
        except Exception:
            model_window = None
        return jsonify({
            "success": True,
            "data": data_out,
            "tool_categories": terminal.get_tool_settings_snapshot(),
            "skills_catalog": skills_catalog,
            "context_compression_settings": {
                **compression_settings,
                "context_window_tokens": min(
                    int(compression_settings.get("deep_trigger_tokens") or 0),
                    int(model_window or compression_settings.get("deep_trigger_tokens") or 0),
                ),
            },
            "thinking_interval_default": THINKING_FAST_INTERVAL,
            "thinking_interval_range": {
                "min": THINKING_INTERVAL_MIN,
                "max": THINKING_INTERVAL_MAX
            },
            "recent_conversations_prompt_limit_range": {
                "min": RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
                "max": RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
            }
        })
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 500

@chat_bp.route('/api/personalization', methods=['POST'])
@api_login_required
@with_terminal
@rate_limited("personalization_update", 20, 300, scope="user")
def update_personalization_settings(terminal: WebTerminal, workspace: UserWorkspace, username: str):
    """更新个性化配置"""
    payload = request.get_json() or {}
    try:
        policy = resolve_admin_policy(get_current_user_record())
        if policy.get("ui_blocks", {}).get("block_personal_space"):
            return jsonify({"success": False, "error": "个人空间已被管理员禁用"}), 403
        config = save_personalization_config(workspace.data_dir, payload)
        private_skills_dir = infer_private_skills_dir(workspace.data_dir)
        skills_catalog = get_skills_catalog(private_dir=private_skills_dir)
        enabled_skills = merge_enabled_skills(
            config.get("enabled_skills"),
            skills_catalog,
            config.get("skills_catalog_snapshot"),
        )
        stored_skills = None if len(enabled_skills) == len(skills_catalog) else enabled_skills
        catalog_snapshot = [item.get("id") for item in skills_catalog if item.get("id")]
        if config.get("enabled_skills") != stored_skills or config.get("skills_catalog_snapshot") != catalog_snapshot:
            config = dict(config)
            config["enabled_skills"] = stored_skills
            config["skills_catalog_snapshot"] = catalog_snapshot
            config = save_personalization_config(workspace.data_dir, config)
        try:
            sync_workspace_skills(workspace.project_path, enabled_skills, private_dir=private_skills_dir)
        except Exception as sync_exc:
            debug_log(f"[Skills] 同步失败: {sync_exc}")
        try:
            terminal.apply_personalization_preferences(config)
            session['run_mode'] = terminal.run_mode
            session['thinking_mode'] = terminal.thinking_mode
            ctx = getattr(terminal, 'context_manager', None)
            if ctx and getattr(ctx, 'current_conversation_id', None):
                try:
                    ctx.conversation_manager.save_conversation(
                        conversation_id=ctx.current_conversation_id,
                        messages=ctx.conversation_history,
                        project_path=str(ctx.project_path),
                        todo_list=ctx.todo_list,
                        thinking_mode=terminal.thinking_mode,
                        run_mode=terminal.run_mode
                    )
                except Exception as meta_exc:
                    debug_log(f"应用个性化偏好失败: 同步对话元数据异常 {meta_exc}")
            try:
                status = terminal.get_status()
                socketio.emit('status_update', status, room=f"user_{username}")
            except Exception as status_exc:
                debug_log(f"广播个性化状态失败: {status_exc}")
        except Exception as exc:
            debug_log(f"应用个性化偏好失败: {exc}")
        config_out = dict(config)
        config_out["enabled_skills"] = enabled_skills
        compression_settings = resolve_context_compression_settings(config_out)
        try:
            model_window = get_model_context_window(getattr(terminal, "model_key", None))
        except Exception:
            model_window = None
        return jsonify({
            "success": True,
            "data": config_out,
            "tool_categories": terminal.get_tool_settings_snapshot(),
            "skills_catalog": skills_catalog,
            "context_compression_settings": {
                **compression_settings,
                "context_window_tokens": min(
                    int(compression_settings.get("deep_trigger_tokens") or 0),
                    int(model_window or compression_settings.get("deep_trigger_tokens") or 0),
                ),
            },
            "thinking_interval_default": THINKING_FAST_INTERVAL,
            "thinking_interval_range": {
                "min": THINKING_INTERVAL_MIN,
                "max": THINKING_INTERVAL_MAX
            },
            "recent_conversations_prompt_limit_range": {
                "min": RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
                "max": RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
            }
        })
    except ValueError as exc:
        return jsonify({"success": False, "error": str(exc)}), 400
    except Exception as exc:
        return jsonify({"success": False, "error": str(exc)}), 500
