# -*- coding: utf-8 -*-
"""
routes.py — 服务监测路由层

API 路由：/api/service-monitor/*

页面路由（/service-monitor/*）已迁移到 Vue SPA，由 server.py 的 SPA 回退处理。

权限：
  - 查看类：登录即可（普通用户仅查看）
  - 操作类（目标增删改、触发巡检、取消）：仅管理员

SSE：/api/service-monitor/run/stream 流式推送巡检进度
"""

from __future__ import annotations

import json
import logging
from datetime import datetime

from flask import (
    Blueprint, session, request, jsonify,
    Response, redirect, url_for, stream_with_context,
)

from .services import target_service, report_service, runner_service, schedule_service, notification_service, statistics_service, compare_service
from utils.audit import log_audit

logger = logging.getLogger("service_monitor.routes")

bp = Blueprint('service-monitor', __name__)


# ============================================================
# 内部辅助：权限
# ============================================================

def _current_user():
    return session.get('user', {})


def _role():
    return _current_user().get('role', '')


def _require_login_json():
    if 'user' not in session:
        return jsonify({"success": False, "error": {"code": 401, "message": "未登录"}}), 401
    return None


def _require_admin_json():
    if 'user' not in session:
        return jsonify({"success": False, "error": {"code": 401, "message": "未登录"}}), 401
    if _role() != 'admin':
        return jsonify({"success": False, "error": {"code": 403, "message": "权限不足，仅管理员可操作"}}), 403
    return None


# ============================================================
# 页面路由（已迁移到 Vue SPA，由 server.py 的 spa_fallback 处理）
# ============================================================

# 以下页面路由已在 Vue SPA 中实现，不再需要 Flask 模板渲染：
#   /service-monitor              → vue: MonitorIndex
#   /service-monitor/targets      → vue: MonitorTargets
#   /service-monitor/reports      → vue: MonitorReports
#   /service-monitor/schedule     → vue: MonitorSchedule
#   /service-monitor/notification → vue: MonitorNotification
#   /service-monitor/statistics   → vue: MonitorStatistics
#   /service-monitor/run/<id>     → vue: MonitorRun
#   /service-monitor/report/<id>  → vue: MonitorReportDetail
#   /service-monitor/compare/<a>/<b> → vue: MonitorCompare


# ============================================================
# API：目标管理（管理员）
# ============================================================

@bp.route('/api/service-monitor/targets', methods=['GET'])
def api_list_targets():
    guard = _require_login_json()
    if guard:
        return guard
    targets = target_service.list_targets(role=_role())
    return jsonify({"success": True, "targets": targets})


@bp.route('/api/service-monitor/targets/<target_id>', methods=['GET'])
def api_get_target(target_id):
    """获取单个目标详情（管理员）。"""
    guard = _require_admin_json()
    if guard:
        return guard
    target = target_service.get_target_view(target_id, role=_role())
    if not target:
        return jsonify({"success": False, "error": {"code": 404, "message": "目标不存在"}}), 404
    return jsonify({"success": True, "target": target})


@bp.route('/api/service-monitor/targets', methods=['POST'])
def api_create_target():
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        target = target_service.create_target(request.get_json(force=True) or {})
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'user': _current_user().get('username', ''),
            'module': 'monitor',
            'action': 'create_target',
            'target': target.get('name', target.get('id', '')),
            'ip': request.remote_addr or '',
        })
        return jsonify({"success": True, "target": target})
    except ValueError as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


@bp.route('/api/service-monitor/targets/<target_id>', methods=['PUT'])
def api_update_target(target_id):
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        target = target_service.update_target(target_id, request.get_json(force=True) or {})
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'user': _current_user().get('username', ''),
            'module': 'monitor',
            'action': 'edit_target',
            'target': target.get('name', target_id),
            'ip': request.remote_addr or '',
        })
        return jsonify({"success": True, "target": target})
    except ValueError as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


@bp.route('/api/service-monitor/targets/<target_id>', methods=['DELETE'])
def api_delete_target(target_id):
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        ok = target_service.delete_target(target_id)
        if not ok:
            return jsonify({"success": False, "error": {"code": 404, "message": "目标不存在"}}), 404
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'user': _current_user().get('username', ''),
            'module': 'monitor',
            'action': 'delete_target',
            'target': target_id,
            'ip': request.remote_addr or '',
        })
        return jsonify({"success": True})
    except ValueError as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


@bp.route('/api/service-monitor/targets/test', methods=['POST'])
def api_test_connection():
    """测试 SSH 连接（管理员）。

    Returns: {
        "success": bool,
        "message": str,
        "detail": str | None,
        "error_code": str | None
    }
    """
    guard = _require_admin_json()
    if guard:
        return guard
    result = target_service.test_connection(request.get_json(force=True) or {})
    return jsonify(result)


# ============================================================
# API：巡检（管理员）
# ============================================================

@bp.route('/api/service-monitor/run/stream', methods=['GET'])
def api_run_stream():
    """SSE 流式巡检。参数：target_id, suite（quick/full）。"""
    guard = _require_admin_json()
    if guard:
        return guard

    target_id = request.args.get('target_id', '')
    suite = request.args.get('suite', 'quick')

    # 防重复触发：同一目标已有未完成的巡检时拒绝新请求
    existing = runner_service.get_running_for_target(target_id)
    if existing:
        err = {"event": "error",
               "message": f"该目标已有巡检正在运行 ({existing['done']}/{existing['total']})，请等待完成或取消后重试",
               "run_id": existing["run_id"]}
        return jsonify(err), 409

    log_audit({
        'timestamp': datetime.now().isoformat(),
        'user': _current_user().get('username', ''),
        'module': 'monitor',
        'action': 'run_check',
        'target': target_id,
        'ip': request.remote_addr or '',
        'suite': suite,
    })

    @stream_with_context
    def generate():
        try:
            for evt in runner_service.run_inspection(target_id, suite):
                yield f"data: {json.dumps(evt, ensure_ascii=False)}\n\n"
        except Exception as e:
            logger.error("SSE 流异常: %s", e)
            err = {"event": "error", "message": f"服务端异常: {e}"}
            yield f"data: {json.dumps(err, ensure_ascii=False)}\n\n"

    return Response(generate(), mimetype='text/event-stream',
                    headers={'Cache-Control': 'no-cache',
                             'X-Accel-Buffering': 'no',
                             'Connection': 'keep-alive'})


@bp.route('/api/service-monitor/run/status/<target_id>', methods=['GET'])
def api_run_status(target_id):
    """查询指定目标是否有正在运行的巡检。

    返回：
      - {"running": true, "run_id", "done", "total", "current"} 或
      - {"running": false}
    """
    guard = _require_admin_json()
    if guard:
        return guard

    run = runner_service.get_running_for_target(target_id)
    if run:
        return jsonify({"running": True, **run})
    return jsonify({"running": False})


@bp.route('/api/service-monitor/run/<run_id>/cancel', methods=['POST'])
def api_cancel_run(run_id):
    guard = _require_admin_json()
    if guard:
        return guard
    ok = runner_service.cancel_run(run_id)
    return jsonify({"success": ok})


# ============================================================
# API：报告（登录即可查看）
# ============================================================

@bp.route('/api/service-monitor/reports', methods=['GET'])
def api_list_reports():
    guard = _require_login_json()
    if guard:
        return guard
    target_id = request.args.get('target_id')
    reports = report_service.list_reports(target_id=target_id, limit=50)
    return jsonify({"success": True, "reports": reports})


@bp.route('/api/service-monitor/reports/<report_id>', methods=['GET'])
def api_get_report(report_id):
    guard = _require_login_json()
    if guard:
        return guard
    report = report_service.get_report(report_id)
    if not report:
        return jsonify({"success": False, "error": {"code": 404, "message": "报告不存在"}}), 404
    return jsonify({"success": True, "report": report})


@bp.route('/api/service-monitor/reports/<report_id>', methods=['DELETE'])
def api_delete_report(report_id):
    guard = _require_admin_json()
    if guard:
        return guard
    ok = report_service.delete_report(report_id)
    return jsonify({"success": ok})


@bp.route('/api/service-monitor/reports/batch', methods=['DELETE'])
def api_batch_delete_reports():
    """批量删除巡检报告（管理员）。"""
    guard = _require_admin_json()
    if guard:
        return guard
    ids = (request.get_json(force=True) or {}).get("ids", [])
    if not ids:
        return jsonify({"success": False, "error": {"code": 400, "message": "未选择报告"}}), 400
    deleted = 0
    for rid in ids:
        if report_service.delete_report(rid):
            deleted += 1
    return jsonify({"success": True, "deleted": deleted})


@bp.route('/api/service-monitor/reports/<report_id>/export', methods=['GET'])
def api_export_report(report_id):
    # token 免登导出（只读操作，允许访客）
    token = request.args.get('token', '').strip()
    if not (token and report_service.validate_access_token(report_id, token)):
        # 常规登录访问
        guard = _require_login_json()
        if guard:
            return guard

    fmt = request.args.get('format', 'md')

    # JSON 格式
    if fmt == 'json':
        data = report_service.export_json(report_id)
        if not data:
            return jsonify({"success": False, "error": {"code": 404, "message": "报告不存在"}}), 404
        resp = Response(json.dumps(data, ensure_ascii=False, indent=2),
                        mimetype='application/json')
        resp.headers['Content-Disposition'] = f'attachment; filename=report_{report_id}.json'
        return resp

    # Excel 格式
    if fmt == 'excel':
        data = report_service.export_excel(report_id)
        if not data:
            return jsonify({"success": False, "error": {"code": 404, "message": "报告不存在"}}), 404
        resp = Response(data,
                        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
        resp.headers['Content-Disposition'] = f'attachment; filename=report_{report_id}.xlsx'
        return resp

    # PDF 格式
    if fmt == 'pdf':
        data = report_service.export_pdf(report_id)
        if not data:
            return jsonify({"success": False, "error": {"code": 404, "message": "报告不存在"}}), 404
        # weasyprint 可用：返回 PDF；降级：返回可打印 HTML
        if data[:4] == b'%PDF':
            resp = Response(data, mimetype='application/pdf')
            resp.headers['Content-Disposition'] = f'attachment; filename=report_{report_id}.pdf'
        else:
            # 降级 HTML（带打印按钮提示）
            html_with_hint = (
                "<!DOCTYPE html><html><head><meta charset='utf-8'>"
                "<style>body{margin:0}</style></head><body>"
                "<div style='position:fixed;top:10px;right:10px;z-index:9999;"
                "background:#1a73e8;color:#fff;padding:8px 16px;border-radius:6px;"
                "font-size:13px;cursor:pointer;font-family:sans-serif'"
                " onclick='window.print()'>🖨 打印 / 保存为 PDF</div>"
                + data.decode('utf-8')
                + "</body></html>"
            )
            resp = Response(html_with_hint, mimetype='text/html')
        return resp

    # Markdown 格式（默认）
    md = report_service.export_markdown(report_id)
    if md is None:
        return jsonify({"success": False, "error": {"code": 404, "message": "报告不存在"}}), 404
    resp = Response(md, mimetype='text/markdown')
    resp.headers['Content-Disposition'] = f'attachment; filename=report_{report_id}.md'
    return resp


# ============================================================
# API：报告对比
# ============================================================

@bp.route('/api/service-monitor/reports/<report_id_a>/compare/<report_id_b>', methods=['GET'])
def api_compare_reports(report_id_a, report_id_b):
    """对比两份报告。"""
    guard = _require_login_json()
    if guard:
        return guard

    data = compare_service.compare_reports(report_id_a, report_id_b)
    if not data:
        return jsonify({"success": False, "error": {"code": 404, "message": "报告不存在"}}), 404

    if data.get("error") == "targets_mismatch":
        return jsonify({"success": False, "error": {"code": 400, "message": "两份报告不属于同一目标"}}), 400

    return jsonify({"success": True, "data": data})


@bp.route('/api/service-monitor/reports/<report_id_a>/compare/<report_id_b>/export', methods=['GET'])
def api_export_compare(report_id_a, report_id_b):
    """导出对比报告。"""
    guard = _require_login_json()
    if guard:
        return guard

    fmt = request.args.get('format', 'md')
    data = compare_service.compare_reports(report_id_a, report_id_b)
    if not data:
        return jsonify({"success": False, "error": {"code": 404, "message": "报告不存在"}}), 404

    if data.get("error") == "targets_mismatch":
        return jsonify({"success": False, "error": {"code": 400, "message": "两份报告不属于同一目标"}}), 400

    if fmt == 'html':
        content = compare_service.export_compare_html(data)
        resp = Response(content, mimetype='text/html')
        resp.headers['Content-Disposition'] = f'attachment; filename=compare_{report_id_a}_{report_id_b}.html'
    else:
        content = compare_service.export_compare_markdown(data)
        resp = Response(content, mimetype='text/markdown')
        resp.headers['Content-Disposition'] = f'attachment; filename=compare_{report_id_a}_{report_id_b}.md'

    return resp


# ============================================================
# 修复预留（本期不实现）
# ============================================================

@bp.route('/api/service-monitor/fix', methods=['POST'])
def api_fix():
    """修复能力预留位——本期返回开发中提示。"""
    guard = _require_admin_json()
    if guard:
        return guard
    return jsonify({"success": False, "message": "修复能力开发中，敬请期待"})


# ============================================================
# API：定时任务（管理员操作，登录可查看）
# ============================================================

@bp.route('/api/service-monitor/schedules', methods=['GET'])
def api_list_schedules():
    guard = _require_login_json()
    if guard:
        return guard
    schedules = schedule_service.list_schedules()
    return jsonify({"success": True, "schedules": schedules})


@bp.route('/api/service-monitor/schedules', methods=['POST'])
def api_create_schedule():
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        sched = schedule_service.create_schedule(
            request.get_json(force=True) or {},
            created_by=_current_user().get('username', 'admin'),
        )
        # 通知调度器重新加载
        schedule_service.reload_job(sched['id'])
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'user': _current_user().get('username', ''),
            'module': 'monitor',
            'action': 'create_schedule',
            'target': sched.get('name', sched.get('id', '')),
            'ip': request.remote_addr or '',
        })
        return jsonify({"success": True, "schedule": sched})
    except ValueError as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


@bp.route('/api/service-monitor/schedules/<schedule_id>', methods=['PUT'])
def api_update_schedule(schedule_id):
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        sched = schedule_service.update_schedule(
            schedule_id, request.get_json(force=True) or {}
        )
        schedule_service.reload_job(schedule_id)
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'user': _current_user().get('username', ''),
            'module': 'monitor',
            'action': 'edit_schedule',
            'target': sched.get('name', schedule_id),
            'ip': request.remote_addr or '',
        })
        return jsonify({"success": True, "schedule": sched})
    except ValueError as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


@bp.route('/api/service-monitor/schedules/<schedule_id>', methods=['DELETE'])
def api_delete_schedule(schedule_id):
    guard = _require_admin_json()
    if guard:
        return guard
    ok = schedule_service.delete_schedule(schedule_id)
    if ok:
        schedule_service.reload_job(schedule_id)  # 会移除 job
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'user': _current_user().get('username', ''),
            'module': 'monitor',
            'action': 'delete_schedule',
            'target': schedule_id,
            'ip': request.remote_addr or '',
        })
    return jsonify({"success": ok})


@bp.route('/api/service-monitor/schedules/<schedule_id>/toggle', methods=['POST'])
def api_toggle_schedule(schedule_id):
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        sched = schedule_service.toggle_enabled(schedule_id)
        schedule_service.reload_job(schedule_id)
        return jsonify({"success": True, "schedule": sched})
    except ValueError as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


@bp.route('/api/service-monitor/schedules/<schedule_id>/run', methods=['POST'])
def api_run_schedule(schedule_id):
    """手动立即执行一次定时任务（后台线程，立即返回）。"""
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        result = schedule_service.run_now(schedule_id)
        return jsonify({"success": True, **result})
    except ValueError as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


# ============================================================
# API：通知配置（管理员）
# ============================================================

@bp.route('/api/service-monitor/notification', methods=['GET'])
def api_get_notification():
    """获取通知配置。"""
    guard = _require_admin_json()
    if guard:
        return guard
    config = notification_service.get_config_masked()
    return jsonify({"success": True, "config": config})


@bp.route('/api/service-monitor/notification', methods=['PUT'])
def api_save_notification():
    """保存通知配置。"""
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        data = request.get_json(force=True) or {}
        config = notification_service.save_config(
            data, updated_by=_current_user().get('username', 'admin')
        )
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'user': _current_user().get('username', ''),
            'module': 'monitor',
            'action': 'config_notification',
            'target': '通知配置',
            'ip': request.remote_addr or '',
        })
        return jsonify({"success": True, "config": config})
    except Exception as e:
        return jsonify({"success": False, "error": {"code": 400, "message": str(e)}}), 400


@bp.route('/api/service-monitor/notification/test-email', methods=['POST'])
def api_test_email():
    """测试邮件通知。"""
    guard = _require_admin_json()
    if guard:
        return guard
    result = notification_service.test_email()
    return jsonify(result)


@bp.route('/api/service-monitor/notification/test-dingtalk', methods=['POST'])
def api_test_dingtalk():
    """测试钉钉通知。"""
    guard = _require_admin_json()
    if guard:
        return guard
    result = notification_service.test_dingtalk()
    return jsonify(result)


@bp.route('/api/service-monitor/notification/test-wecom', methods=['POST'])
def api_test_wecom():
    """测试企业微信通知。"""
    guard = _require_admin_json()
    if guard:
        return guard
    result = notification_service.test_wecom()
    return jsonify(result)


@bp.route('/api/service-monitor/notification/check-missing-reports', methods=['GET'])
def api_check_missing_reports():
    """检查并返回缺失报告的目标列表。"""
    guard = _require_admin_json()
    if guard:
        return guard
    try:
        from .services import report_service
        config = notification_service.get_config()
        rm_cfg = config.get("report_missing", {})
        missing_days = int(rm_cfg.get("missing_days", 2) or 2)
        missing = report_service.check_missing_reports(missing_days=missing_days)
        return jsonify({"success": True, "data": {"missing_targets": missing}})
    except Exception as e:
        return jsonify({"success": False, "error": {"code": 500, "message": str(e)}}), 500


# ============================================================
# API：监测统计（登录即可查看）
# ============================================================

@bp.route('/api/service-monitor/statistics/overview', methods=['GET'])
def api_statistics_overview():
    """概览统计。"""
    guard = _require_login_json()
    if guard:
        return guard
    target_id = request.args.get('target_id') or None
    start_date = request.args.get('start_date') or None
    end_date = request.args.get('end_date') or None
    data = statistics_service.get_overview(target_id, start_date, end_date)
    return jsonify({"success": True, "data": data})


@bp.route('/api/service-monitor/statistics/modules', methods=['GET'])
def api_statistics_modules():
    """模块健康统计。"""
    guard = _require_login_json()
    if guard:
        return guard
    target_id = request.args.get('target_id') or None
    start_date = request.args.get('start_date') or None
    end_date = request.args.get('end_date') or None
    data = statistics_service.get_module_stats(target_id, start_date, end_date)
    return jsonify({"success": True, "data": data})


@bp.route('/api/service-monitor/statistics/item-trend', methods=['GET'])
def api_statistics_item_trend():
    """检测项趋势。"""
    guard = _require_login_json()
    if guard:
        return guard
    target_id = request.args.get('target_id') or None
    item_key = request.args.get('item_key', '')
    start_date = request.args.get('start_date') or None
    end_date = request.args.get('end_date') or None
    if not item_key:
        return jsonify({"success": False, "error": {"code": 400, "message": "缺少 item_key 参数"}}), 400
    data = statistics_service.get_item_trend(target_id, item_key, start_date, end_date)
    return jsonify({"success": True, "data": data})


@bp.route('/api/service-monitor/statistics/health-trend', methods=['GET'])
def api_statistics_health_trend():
    """健康度趋势。"""
    guard = _require_login_json()
    if guard:
        return guard
    target_id = request.args.get('target_id') or None
    start_date = request.args.get('start_date') or None
    end_date = request.args.get('end_date') or None
    data = statistics_service.get_health_trend(target_id, start_date, end_date)
    return jsonify({"success": True, "data": data})


@bp.route('/api/service-monitor/statistics/abnormal-items', methods=['GET'])
def api_statistics_abnormal_items():
    """高频异常检测项。"""
    guard = _require_login_json()
    if guard:
        return guard
    target_id = request.args.get('target_id') or None
    start_date = request.args.get('start_date') or None
    end_date = request.args.get('end_date') or None
    data = statistics_service.get_abnormal_items(target_id, start_date, end_date)
    return jsonify({"success": True, "data": data})


@bp.route('/api/service-monitor/statistics/item-keys', methods=['GET'])
def api_statistics_item_keys():
    """检测项键列表（用于选择器）。"""
    guard = _require_login_json()
    if guard:
        return guard
    target_id = request.args.get('target_id') or None
    start_date = request.args.get('start_date') or None
    end_date = request.args.get('end_date') or None
    data = statistics_service.get_item_keys_for_target(target_id, start_date, end_date)
    return jsonify({"success": True, "data": data})
