# -*- coding: utf-8 -*-
"""
server.py — 问题排查助手 Web 服务

功能：
  提供 Web API 接口：
    - POST /api/troubleshoot — 接收问题描述，返回排查指引
    - POST /api/submit        — 接收问题记录提交，写入问题库

启动：
  python server.py

访问：
  http://localhost:8088
"""

import os
import re
import sys
import json
import time
import uuid
import traceback
from pathlib import Path
from datetime import datetime

from flask import Flask, request, jsonify, render_template, send_from_directory, send_file, session, redirect, url_for
from io import BytesIO

# ============================================================
# 认证模块导入
# ============================================================
from auth import user_manager
from decorators import login_required, admin_required, page_login_required

# ============================================================
# 日志与统一响应工具
# ============================================================
from utils.logger import get_logger
from utils.error_codes import ErrorCodes
from utils.response import error_response, success_response

logger = get_logger(__name__)

# ============================================================
# 配置
# ============================================================

# 路径常量集中管理（utils/paths.py）—— P1-3 步骤1 抽取
from utils.paths import (
    SCRIPT_DIR, DATA_DIR, PROJECT_ROOT, RECORDS_DIR, CONFIG_FILE, AUDIT_LOG_FILE,
)

# Vue SPA 前端构建输出目录
# 注：PROJECT_ROOT 在开发环境可能解析到 skill/code/，需要手动向上查找仓库根
_VUE_DIST_CANDIDATES = [
    PROJECT_ROOT / "frontend" / "dist",
    PROJECT_ROOT.parent / "frontend" / "dist",  # 开发环境：skill/code -> 仓库根
    PROJECT_ROOT.parent.parent / "frontend" / "dist",  # 备用
]
VUE_DIST_DIR = next((p for p in _VUE_DIST_CANDIDATES if p.exists()), _VUE_DIST_CANDIDATES[0])

# ============================================================
# 初始化
# ============================================================

# 强制 UTF-8 编码
if hasattr(sys.stdout, 'reconfigure'):
    sys.stdout.reconfigure(encoding='utf-8')


def create_app():
    """Flask 应用工厂 — 创建并配置 app，注册所有 Blueprint。

    模块级 `app = create_app()` 保持 `server.app` 引用向后兼容；
    工厂模式便于未来测试 fixture 与多实例部署。
    """
    app = Flask(
        __name__,
        template_folder=str(SCRIPT_DIR / "templates"),
        static_folder=str(SCRIPT_DIR / "static"),
    )

    # Session 配置
    app.secret_key = os.environ.get('SECRET_KEY')
    if not app.secret_key:
        import secrets
        app.secret_key = secrets.token_hex(32)
        print("[警告] SECRET_KEY 环境变量未设置，已生成随机密钥")
        print("[提示] 生产环境请设置 SECRET_KEY 环境变量")
    app.config['SESSION_TYPE'] = 'filesystem'
    app.config['PERMANENT_SESSION_LIFETIME'] = 86400  # 24小时

    # CORS 配置（开发环境）
    cors_origins = os.environ.get('CORS_ORIGINS', 'http://localhost:5173')
    if cors_origins:
        try:
            from flask_cors import CORS
            CORS(app, origins=cors_origins.split(','), supports_credentials=True)
            logger.info(f"CORS 已启用，允许源: {cors_origins}")
        except ImportError:
            logger.warning("flask-cors 未安装，跳过 CORS 配置")

    # 注册 Blueprint（url_prefix 留空，保持 API 路径不变）
    from routes.platform import bp as platform_bp
    from routes.auth import bp as auth_bp
    from routes.troubleshoot import bp as troubleshoot_bp
    from routes.service_manage import bp as service_manage_bp
    from service_monitor import bp as service_monitor_bp
    from routes.cache import bp as cache_bp
    from routes.export import bp as export_bp
    from routes.submit import bp as submit_bp
    from routes.audit import bp as audit_bp
    from routes.version import bp as version_bp
    app.register_blueprint(platform_bp)
    app.register_blueprint(auth_bp)
    app.register_blueprint(troubleshoot_bp)
    app.register_blueprint(service_manage_bp)
    app.register_blueprint(service_monitor_bp)
    app.register_blueprint(cache_bp)
    app.register_blueprint(export_bp)
    app.register_blueprint(submit_bp)
    app.register_blueprint(audit_bp)
    app.register_blueprint(version_bp)

    # 初始化服务监测定时调度
    from service_monitor.services import schedule_service
    schedule_service.init_scheduler(app)

    # ============================================================
    # SPA 回退路由（Vue 前端接管所有非 API 路由）
    # ============================================================

    def _vue_built():
        """检查 Vue 前端是否已构建"""
        return VUE_DIST_DIR.exists() and (VUE_DIST_DIR / 'index.html').exists()

    @app.route('/login', methods=['GET'])
    def spa_login():
        """登录页面 — Vue 构建存在返回 SPA，否则回退 Flask 模板"""
        # 已登录则重定向到主页
        if 'user' in session:
            next_url = request.args.get('next', '/')
            if not next_url.startswith('/') or next_url.startswith('//'):
                next_url = '/'
            return redirect(next_url)

        # Vue SPA 前端
        if _vue_built():
            return send_from_directory(VUE_DIST_DIR, 'index.html')

        # 回退到 Flask 模板（开发阶段或 Vue 未构建时）
        next_url = request.args.get('next', '')
        return render_template('login.html', next_url=next_url)

    @app.route('/', defaults={'path': ''})
    @app.route('/<path:path>')
    def spa_fallback(path):
        """SPA 回退路由 — 所有非 API/非静态文件请求返回 Vue index.html

        Vue 构建存在时：返回 SPA index.html（前端路由接管）
        Vue 未构建时：回退到 Flask 模板渲染（兼容旧测试与未构建环境）

        排除以下路由：
          - /api/* — API 路由由各 Blueprint 处理
          - /login — 登录页面（单独处理）
          - /logout — API 路由
          - /static/* — Flask 静态文件
        """
        # API 路由、静态文件、登录/注销由其他处理器处理
        if path.startswith('api/') or path.startswith('static/'):
            return jsonify({"success": False, "error": {"code": 404, "message": "Not Found"}}), 404
        if path in ('login', 'logout'):
            return jsonify({"success": False, "error": {"code": 404, "message": "Not Found"}}), 404

        # Vue SPA 前端（已构建）
        if _vue_built():
            return send_from_directory(VUE_DIST_DIR, 'index.html')

        # ===== Vue 未构建时的 Flask 模板回退 =====
        # 未登录重定向到 /login（保持与旧 platform.index 一致）
        if 'user' not in session:
            return redirect(url_for('spa_login', next=request.path))

        # 已登录：根据 path 渲染对应的 Flask 模板
        return _render_flask_fallback(path)

    def _render_flask_fallback(path):
        """Vue 未构建时，回退渲染对应 Flask 模板（兼容旧路由行为）"""
        import container as _container
        from utils.modules import get_modules as _get_modules

        user = session.get('user', {})
        role = user.get('role', '')

        # 平台首页
        if path == '' or path == '/':
            modules = _get_modules(role=role)
            return render_template('platform.html',
                                   modules=modules, user=user,
                                   platform_name="运行维护平台")

        # 问题排查主页
        if path == 'troubleshoot':
            return render_template('index.html',
                                   projects=_container.get_search_engine().get_projects(),
                                   system_types=_container.get_config().get('system_types', []))

        # 服务管理页面
        if path == 'service-manage':
            return redirect(url_for('service-manage.page_authorization'))
        if path == 'service-manage/authorization':
            return render_template('service_manage/authorization.html',
                                   user=user, is_admin=(role == 'admin'),
                                   active_menu='authorization')
        if path == 'service-manage/upgrade':
            return render_template('service_manage/upgrade.html',
                                   user=user, is_admin=(role == 'admin'),
                                   active_menu='upgrade')
        if path == 'service-manage/info':
            return render_template('service_manage/info.html',
                                   user=user, is_admin=(role == 'admin'),
                                   active_menu='info')

        # 服务监测页面
        from service_monitor.services import target_service, report_service, schedule_service, notification_service
        is_admin = (role == 'admin')

        if path == 'service-monitor':
            targets = target_service.list_targets(role=role)
            return render_template('service_monitor/index.html',
                                   user=user, targets=targets,
                                   is_admin=is_admin, active_menu='targets')
        if path == 'service-monitor/targets':
            if not is_admin:
                return redirect('/service-monitor')
            targets = target_service.list_targets(role='admin')
            return render_template('service_monitor/targets.html',
                                   user=user, targets=targets,
                                   is_admin=True, active_menu='manage')
        if path == 'service-monitor/reports':
            target_id = request.args.get('target_id')
            reports = report_service.list_reports(target_id=target_id, limit=50)
            targets = target_service.list_targets(role=role)
            return render_template('service_monitor/reports.html',
                                   user=user, reports=reports, targets=targets,
                                   is_admin=is_admin, active_menu='reports')
        if path == 'service-monitor/schedule':
            schedules = schedule_service.list_schedules()
            _STATUS_MAP = {
                "running": ("badge-running", "🔄 执行中"),
                "success": ("badge-success", "✅ 成功"),
                "failed":  ("badge-failed",  "❌ 失败"),
                "error":   ("badge-error",   "⚠️ 异常"),
                "idle":    ("badge-idle",    "⏸ 待执行"),
            }
            for s in schedules:
                s['description'] = schedule_service._describe_schedule(s)
                status_key = s.get("current_status") or s.get("last_run_status") or "idle"
                badge_cls, label = _STATUS_MAP.get(status_key, ("badge-idle", "⏸ 待执行"))
                s['run_badge'] = badge_cls
                s['run_label'] = label
            targets = target_service.list_targets(role=role)
            return render_template('service_monitor/schedule.html',
                                   user=user, schedules=schedules, targets=targets,
                                   is_admin=is_admin, active_menu='schedule')
        if path == 'service-monitor/notification':
            if not is_admin:
                return redirect('/service-monitor')
            config = notification_service.get_config_masked()
            return render_template('service_monitor/notification.html',
                                   user=user, config=config,
                                   is_admin=True, active_menu='notification')
        if path == 'service-monitor/statistics':
            targets = target_service.list_targets(role=role)
            return render_template('service_monitor/statistics.html',
                                   user=user, targets=targets,
                                   is_admin=is_admin, active_menu='statistics')

        # 带参数的路由：run/<id>、report/<id>、compare/<a>/<b>
        if path.startswith('service-monitor/run/'):
            if not is_admin:
                return redirect('/service-monitor')
            target_id = path.split('/')[-1]
            target = target_service.get_target_view(target_id, role='admin')
            if not target:
                return redirect('/service-monitor')
            suite = request.args.get('suite', 'quick')
            return render_template('service_monitor/run.html',
                                   user=user, target=target, suite=suite,
                                   is_admin=True, active_menu='targets')

        if path.startswith('service-monitor/report/'):
            report_id = path.split('/')[-1]
            report = report_service.get_report(report_id)
            if not report:
                return redirect('/service-monitor')
            return render_template('service_monitor/report.html',
                                   user=user, report=report,
                                   is_admin=is_admin, active_menu='reports',
                                   token_auth=False)

        if path.startswith('service-monitor/compare/'):
            parts = path.split('/')
            if len(parts) >= 4:
                report_id_a, report_id_b = parts[2], parts[3]
                from service_monitor.services import compare_service
                data = compare_service.compare_reports(report_id_a, report_id_b)
                if not data:
                    return redirect('/service-monitor/reports')
                if data.get("error") == "targets_mismatch":
                    return render_template('service_monitor/compare.html',
                                           user=user, error="目标不匹配",
                                           is_admin=is_admin, active_menu='reports')
                return render_template('service_monitor/compare.html',
                                       user=user, data=data,
                                       is_admin=is_admin, active_menu='reports')

        # 未知路径：回退到平台首页
        return redirect('/')

    return app


# 模块级 app（启动即创建，保持 server.app 向后兼容）
app = create_app()

# 加载配置
# ============================================================
# 配置与单例（已迁至 container.py —— P1-3 步骤2）
# ============================================================

from container import (
    load_config, get_config, get_search_engine, reset_search_engine,
    get_safety_filter, get_cache_manager, get_skill_md,
)

# 模块级 config（启动即加载，保持原行为）
config = get_config()

# ============================================================
# Prompt 构建与 Claude API 调用（已迁至 services/ai_service.py —— P1-3 步骤4）
# ============================================================

from services.ai_service import (
    build_prompt, call_claude_api, call_claude_api_stream,
    generate_mock_response, split_mock_response,
)


# ============================================================
# 启动
# ============================================================

if __name__ == '__main__':
    print("="*50)
    print("🔍 问题排查助手 Web 服务")
    print("="*50)
    print(f"📁 项目根目录：{PROJECT_ROOT}")
    print(f"📊 知识库：{len(get_search_engine().records)} 条记录")
    print(f"🌐 服务地址：http://localhost:8088")
    print("="*50)

    # debug 从环境变量读取（默认关闭，生产安全；开发用 FLASK_DEBUG=1 开启）
    # debug 模式的 reloader 会导致 APScheduler 在父进程初始化后丢失，定时任务失效
    debug = os.environ.get('FLASK_DEBUG', '0') == '1'

    app.run(
        host='0.0.0.0',
        port=8088,
        debug=debug,
        threaded=True,
    )