#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations

from typing import Any

from flask import Blueprint, jsonify, request

import db_pool
from db import security as security_db
from routes.decorators import admin_required
from security import BlacklistManager, RiskScorer

security_bp = Blueprint("admin_security", __name__)
blacklist = BlacklistManager()
scorer = RiskScorer(blacklist_manager=blacklist)


def _truncate(value: Any, max_len: int = 200) -> str:
    text = str(value or "")
    if max_len <= 0:
        return ""
    if len(text) <= max_len:
        return text
    return text[: max(0, max_len - 3)] + "..."


def _parse_int_arg(name: str, default: int, *, min_value: int | None = None, max_value: int | None = None) -> int:
    raw = request.args.get(name, None)
    if raw is None or str(raw).strip() == "":
        value = int(default)
    else:
        try:
            value = int(str(raw).strip())
        except Exception:
            value = int(default)

    if min_value is not None:
        value = max(int(min_value), value)
    if max_value is not None:
        value = min(int(max_value), value)
    return value


def _parse_json() -> dict:
    if request.is_json:
        data = request.get_json(silent=True) or {}
        return data if isinstance(data, dict) else {}
    # 兼容 form-data
    try:
        return dict(request.form or {})
    except Exception:
        return {}


def _parse_bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, int):
        return value != 0
    text = str(value or "").strip().lower()
    return text in {"1", "true", "yes", "y", "on"}


def _parse_int(value: Any, *, default: int | None = None, min_value: int | None = None) -> int | None:
    try:
        parsed = int(value)
    except Exception:
        parsed = default

    if parsed is None:
        return None
    if min_value is not None:
        parsed = max(int(min_value), parsed)
    return parsed


def _sanitize_threat_event(event: dict) -> dict:
    return {
        "id": event.get("id"),
        "threat_type": event.get("threat_type") or "unknown",
        "score": int(event.get("score") or 0),
        "ip": _truncate(event.get("ip"), 64),
        "user_id": event.get("user_id"),
        "request_method": _truncate(event.get("request_method"), 16),
        "request_path": _truncate(event.get("request_path"), 256),
        "field_name": _truncate(event.get("field_name"), 80),
        "rule": _truncate(event.get("rule"), 120),
        "matched": _truncate(event.get("matched"), 120),
        "value_preview": _truncate(event.get("value_preview"), 200),
        "created_at": event.get("created_at"),
    }


def _sanitize_ban_entry(entry: dict, *, kind: str) -> dict:
    if kind == "ip":
        return {
            "ip": _truncate(entry.get("ip"), 64),
            "reason": _truncate(entry.get("reason"), 200),
            "added_at": entry.get("added_at"),
            "expires_at": entry.get("expires_at"),
            "is_active": int(entry.get("is_active") or 0),
        }
    if kind == "user":
        return {
            "user_id": entry.get("user_id"),
            "reason": _truncate(entry.get("reason"), 200),
            "added_at": entry.get("added_at"),
            "expires_at": entry.get("expires_at"),
            "is_active": int(entry.get("is_active") or 0),
        }
    return {}


@security_bp.route("/api/admin/security/dashboard", methods=["GET"])
@admin_required
def get_security_dashboard():
    """
    获取安全仪表板数据
    返回:
    - 最近24小时威胁事件数
    - 当前封禁IP数
    - 当前封禁用户数
    - 最近10条威胁事件
    """
    try:
        threat_24h = security_db.get_threat_events_count(hours=24)
    except Exception:
        threat_24h = 0

    try:
        banned_ips = blacklist.get_banned_ips()
    except Exception:
        banned_ips = []

    try:
        banned_users = blacklist.get_banned_users()
    except Exception:
        banned_users = []

    try:
        recent = security_db.get_threat_events_list(page=1, per_page=10, filters={}).get("items", [])
        recent_items = [_sanitize_threat_event(e) for e in recent if isinstance(e, dict)]
    except Exception:
        recent_items = []

    return jsonify(
        {
            "threat_events_24h": int(threat_24h or 0),
            "banned_ip_count": len(banned_ips),
            "banned_user_count": len(banned_users),
            "recent_threat_events": recent_items,
        }
    )


@security_bp.route("/api/admin/security/threats", methods=["GET"])
@admin_required
def get_threat_events():
    """
    获取威胁事件列表（分页）
    参数: page, per_page, severity, event_type
    """
    page = _parse_int_arg("page", 1, min_value=1, max_value=100000)
    per_page = _parse_int_arg("per_page", 20, min_value=1, max_value=200)
    severity = (request.args.get("severity") or "").strip()
    event_type = (request.args.get("event_type") or "").strip()

    filters: dict[str, Any] = {}
    if severity:
        filters["severity"] = severity
    if event_type:
        filters["event_type"] = event_type

    data = security_db.get_threat_events_list(page, per_page, filters)
    items = data.get("items") or []
    data["items"] = [_sanitize_threat_event(e) for e in items if isinstance(e, dict)]
    return jsonify(data)


@security_bp.route("/api/admin/security/banned-ips", methods=["GET"])
@admin_required
def get_banned_ips():
    """获取封禁IP列表"""
    items = blacklist.get_banned_ips()
    return jsonify({"count": len(items), "items": [_sanitize_ban_entry(x, kind="ip") for x in items]})


@security_bp.route("/api/admin/security/banned-users", methods=["GET"])
@admin_required
def get_banned_users():
    """获取封禁用户列表"""
    items = blacklist.get_banned_users()
    return jsonify({"count": len(items), "items": [_sanitize_ban_entry(x, kind="user") for x in items]})


@security_bp.route("/api/admin/security/ban-ip", methods=["POST"])
@admin_required
def ban_ip():
    """
    手动封禁IP
    参数: ip, reason, duration_hours(可选), permanent(可选)
    """
    data = _parse_json()
    ip = str(data.get("ip") or "").strip()
    reason = str(data.get("reason") or "").strip()
    duration_hours_raw = data.get("duration_hours", 24)
    permanent = _parse_bool(data.get("permanent", False))

    if not ip:
        return jsonify({"error": "ip不能为空"}), 400
    if not reason:
        return jsonify({"error": "reason不能为空"}), 400

    duration_hours = _parse_int(duration_hours_raw, default=24, min_value=1) or 24

    ok = blacklist.ban_ip(ip, reason, duration_hours=duration_hours, permanent=permanent)
    if not ok:
        return jsonify({"error": "封禁失败"}), 400
    return jsonify({"success": True})


@security_bp.route("/api/admin/security/unban-ip", methods=["POST"])
@admin_required
def unban_ip():
    """解除IP封禁"""
    data = _parse_json()
    ip = str(data.get("ip") or "").strip()
    if not ip:
        return jsonify({"error": "ip不能为空"}), 400

    ok = blacklist.unban_ip(ip)
    if not ok:
        return jsonify({"error": "未找到封禁记录"}), 404
    return jsonify({"success": True})


@security_bp.route("/api/admin/security/ban-user", methods=["POST"])
@admin_required
def ban_user():
    """手动封禁用户"""
    data = _parse_json()
    user_id_raw = data.get("user_id")
    reason = str(data.get("reason") or "").strip()
    duration_hours_raw = data.get("duration_hours", 24)
    permanent = _parse_bool(data.get("permanent", False))

    user_id = _parse_int(user_id_raw)

    if user_id is None:
        return jsonify({"error": "user_id不能为空"}), 400
    if not reason:
        return jsonify({"error": "reason不能为空"}), 400

    duration_hours = _parse_int(duration_hours_raw, default=24, min_value=1) or 24

    ok = blacklist._ban_user_internal(user_id, reason=reason, duration_hours=duration_hours, permanent=permanent)
    if not ok:
        return jsonify({"error": "封禁失败"}), 400
    return jsonify({"success": True})


@security_bp.route("/api/admin/security/unban-user", methods=["POST"])
@admin_required
def unban_user():
    """解除用户封禁"""
    data = _parse_json()
    user_id_raw = data.get("user_id")
    user_id = _parse_int(user_id_raw)

    if user_id is None:
        return jsonify({"error": "user_id不能为空"}), 400

    ok = blacklist.unban_user(user_id)
    if not ok:
        return jsonify({"error": "未找到封禁记录"}), 404
    return jsonify({"success": True})


@security_bp.route("/api/admin/security/ip-risk/<ip>", methods=["GET"])
@admin_required
def get_ip_risk(ip):
    """获取指定IP的风险评分和历史事件"""
    ip_text = str(ip or "").strip()
    if not ip_text:
        return jsonify({"error": "ip不能为空"}), 400

    history = security_db.get_ip_threat_history(ip_text)
    return jsonify(
        {
            "ip": _truncate(ip_text, 64),
            "risk_score": int(scorer.get_ip_score(ip_text) or 0),
            "is_banned": bool(blacklist.is_ip_banned(ip_text)),
            "threat_history": [_sanitize_threat_event(e) for e in history if isinstance(e, dict)],
        }
    )


@security_bp.route("/api/admin/security/ip-risk/clear", methods=["POST"])
@admin_required
def clear_ip_risk():
    """清除指定IP的风险分"""
    data = _parse_json()
    ip_text = str(data.get("ip") or "").strip()
    if not ip_text:
        return jsonify({"error": "ip不能为空"}), 400

    if not scorer.reset_ip_score(ip_text):
        return jsonify({"error": "清理失败"}), 400

    return jsonify({"success": True, "ip": _truncate(ip_text, 64), "risk_score": 0})


@security_bp.route("/api/admin/security/user-risk/<int:user_id>", methods=["GET"])
@admin_required
def get_user_risk(user_id):
    """获取指定用户的风险评分和历史事件"""
    history = security_db.get_user_threat_history(user_id)
    return jsonify(
        {
            "user_id": int(user_id),
            "risk_score": int(scorer.get_user_score(user_id) or 0),
            "is_banned": bool(blacklist.is_user_banned(user_id)),
            "threat_history": [_sanitize_threat_event(e) for e in history if isinstance(e, dict)],
        }
    )


@security_bp.route("/api/admin/security/cleanup", methods=["POST"])
@admin_required
def cleanup_expired():
    """清理过期的封禁记录和衰减风险分"""
    try:
        blacklist.cleanup_expired()
    except Exception:
        pass
    try:
        scorer.decay_scores()
    except Exception:
        pass

    # 可选：返回当前连接池统计信息，便于排查后台运行状态
    pool_stats = None
    try:
        pool_stats = db_pool.get_pool_stats()
    except Exception:
        pool_stats = None

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