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

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

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

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

from __future__ import annotations

import json
import logging

from flask import (
    Blueprint, render_template, 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

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


# ============================================================
# 页面路由
# ============================================================

@bp.route('/service-monitor')
def page_index():
    """监测主页：目标概览。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()
    targets = target_service.list_targets(role=user.get('role', ''))
    return render_template(
        'service_monitor/index.html',
        user=user, targets=targets,
        is_admin=(user.get('role') == 'admin'),
        active_menu='targets',
    )


@bp.route('/service-monitor/targets')
def page_targets():
    """目标管理页（仅管理员可进入，普通用户重定向回主页）。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()
    if user.get('role') != 'admin':
        return redirect(url_for('service-monitor.page_index'))
    targets = target_service.list_targets(role='admin')
    return render_template(
        'service_monitor/targets.html', user=user, targets=targets,
        is_admin=True, active_menu='manage',
    )


@bp.route('/service-monitor/reports')
def page_reports():
    """巡检报告列表页（所有登录用户可看）。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()
    target_id = request.args.get('target_id')
    reports = report_service.list_reports(target_id=target_id, limit=50)
    targets = target_service.list_targets(role=user.get('role', ''))
    return render_template(
        'service_monitor/reports.html',
        user=user, reports=reports, targets=targets,
        is_admin=(user.get('role') == 'admin'),
        active_menu='reports',
    )


@bp.route('/service-monitor/schedule')
def page_schedule():
    """定时任务管理页（所有登录用户可看，操作仅管理员）。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()
    schedules = schedule_service.list_schedules()
    # 为每个 schedule 生成自然语言描述 + 执行状态徽章
    _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)
        # current_status 优先，其次 last_run_status，默认 idle
        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=user.get('role', ''))
    return render_template(
        'service_monitor/schedule.html',
        user=user, schedules=schedules, targets=targets,
        is_admin=(user.get('role') == 'admin'),
        active_menu='schedule',
    )


@bp.route('/service-monitor/notification')
def page_notification():
    """通知配置页面（仅管理员）。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()
    if user.get('role') != 'admin':
        return redirect(url_for('service-monitor.page_index'))
    config = notification_service.get_config_masked()
    return render_template(
        'service_monitor/notification.html',
        user=user, config=config,
        is_admin=True, active_menu='notification',
    )


@bp.route('/service-monitor/statistics')
def page_statistics():
    """监测统计页（所有登录用户可看）。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()
    targets = target_service.list_targets(role=user.get('role', ''))
    return render_template(
        'service_monitor/statistics.html',
        user=user, targets=targets,
        is_admin=(user.get('role') == 'admin'),
        active_menu='statistics',
    )


@bp.route('/service-monitor/run/<target_id>')
def page_run(target_id):
    """巡检执行页（仅管理员）。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()
    if user.get('role') != 'admin':
        return redirect(url_for('service-monitor.page_index'))
    target = target_service.get_target_view(target_id, role='admin')
    if not target:
        return redirect(url_for('service-monitor.page_index'))
    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')


@bp.route('/service-monitor/report/<report_id>')
def page_report(report_id):
    """报告详情页（支持 token 免登访问）。"""
    token = request.args.get('token', '').strip()

    # 1. 尝试 token 免登访问
    if token and report_service.validate_access_token(report_id, token):
        report = report_service.get_report(report_id)
        if not report:
            return redirect(url_for('auth.login'))
        return render_template(
            'service_monitor/report.html',
            user=None,  # 无登录用户
            report=report,
            is_admin=False,
            active_menu='reports',
            token_auth=True,  # 标记访客模式
            access_token=token,  # 传递 token 用于导出等只读操作
        )

    # 2. 常规登录访问
    if 'user' not in session:
        # 携带 next 参数，登录后回到报告页
        next_url = request.url  # 完整 URL（含 token）
        return redirect(url_for('auth.login', next=next_url))
    user = _current_user()
    report = report_service.get_report(report_id)
    if not report:
        return redirect(url_for('service-monitor.page_index'))
    return render_template(
        'service_monitor/report.html', user=user, report=report,
        is_admin=(user.get('role') == 'admin'),
        active_menu='reports',
        token_auth=False,
    )


# ============================================================
# 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', 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 {})
        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 {})
        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
        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

    @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 token and report_service.validate_access_token(report_id, token):
        fmt = request.args.get('format', 'md')
        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
        else:
            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

    # 常规登录访问
    guard = _require_login_json()
    if guard:
        return guard
    fmt = request.args.get('format', 'md')
    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
    else:
        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('/service-monitor/compare/<report_id_a>/<report_id_b>')
def page_compare(report_id_a, report_id_b):
    """报告对比页面。"""
    if 'user' not in session:
        return redirect(url_for('auth.login'))
    user = _current_user()

    data = compare_service.compare_reports(report_id_a, report_id_b)
    if not data:
        return redirect(url_for('service-monitor.page_reports'))

    if data.get("error") == "targets_mismatch":
        return render_template(
            'service_monitor/compare.html',
            user=user,
            error="目标不匹配",
            is_admin=(user.get('role') == 'admin'),
            active_menu='reports',
        )

    return render_template(
        'service_monitor/compare.html',
        user=user,
        data=data,
        is_admin=(user.get('role') == 'admin'),
        active_menu='reports',
    )


@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'])
        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)
        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
    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')
        )
        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)


# ============================================================
# 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/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})
