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

import json
import time

import database
import email_service
from app_logger import get_logger
from app_security import get_rate_limit_ip, require_ip_not_locked, validate_email, validate_password
from flask import Blueprint, jsonify, request, session
from flask_login import current_user, login_required
from routes.pages import render_app_spa_or_legacy
from services.passkeys import (
    MAX_PASSKEYS_PER_OWNER,
    encode_credential_id,
    get_credential_transports,
    get_expected_origins,
    get_rp_id,
    is_challenge_valid,
    make_registration_options,
    normalize_device_name,
    verify_registration,
)
from services.state import check_email_rate_limit, check_ip_request_rate, safe_iter_task_status_items
from services.tasks import get_task_scheduler

logger = get_logger("app")

api_user_bp = Blueprint("api_user", __name__)
_USER_PASSKEY_REGISTER_SESSION_KEY = "user_passkey_register_state"


def _get_current_user_record():
    return database.get_user_by_id(current_user.id)


def _get_current_user_or_404():
    user = _get_current_user_record()
    if user:
        return user, None
    return None, (jsonify({"error": "用户不存在"}), 404)


def _get_current_username(*, fallback: str) -> str:
    user = _get_current_user_record()
    username = (user or {}).get("username", "")
    return username if username else fallback


def _coerce_binary_flag(value, *, field_label: str):
    if isinstance(value, bool):
        value = 1 if value else 0
    try:
        value = int(value)
    except Exception:
        return None, f"{field_label}必须是0或1"
    if value not in (0, 1):
        return None, f"{field_label}必须是0或1"
    return value, None


def _parse_credential_payload(data: dict) -> dict | None:
    credential = data.get("credential")
    if isinstance(credential, dict):
        return credential
    if isinstance(credential, str):
        try:
            parsed = json.loads(credential)
            return parsed if isinstance(parsed, dict) else None
        except Exception:
            return None
    return None


def _truncate_text(value, max_len: int = 300) -> str:
    text = str(value or "").strip()
    if len(text) > max_len:
        return f"{text[:max_len]}..."
    return text


def _check_bind_email_rate_limits(email: str):
    client_ip = get_rate_limit_ip()
    allowed, error_msg = check_ip_request_rate(client_ip, "email")
    if not allowed:
        return False, error_msg, 429
    allowed, error_msg = check_email_rate_limit(email, "bind_email")
    if not allowed:
        return False, error_msg, 429
    return True, "", 200


def _render_verify_bind_failed(*, title: str, error_message: str):
    spa_initial_state = {
        "page": "verify_result",
        "success": False,
        "title": title,
        "error_message": error_message,
        "primary_label": "返回登录",
        "primary_url": "/login",
    }
    return render_app_spa_or_legacy(
        "verify_failed.html",
        legacy_context={"error_message": error_message},
        spa_initial_state=spa_initial_state,
    )


def _render_verify_bind_success(email: str):
    spa_initial_state = {
        "page": "verify_result",
        "success": True,
        "title": "邮箱绑定成功",
        "message": f"邮箱 {email} 已成功绑定到您的账号！",
        "primary_label": "返回登录",
        "primary_url": "/login",
        "redirect_url": "/login",
        "redirect_seconds": 5,
    }
    return render_app_spa_or_legacy("verify_success.html", spa_initial_state=spa_initial_state)


def _get_current_running_count(user_id: int) -> int:
    try:
        queue_snapshot = get_task_scheduler().get_queue_state_snapshot() or {}
        running_by_user = queue_snapshot.get("running_by_user") or {}
        return int(running_by_user.get(int(user_id), running_by_user.get(str(user_id), 0)) or 0)
    except Exception:
        current_running = 0
        for _, info in safe_iter_task_status_items():
            if info.get("user_id") == user_id and info.get("status") == "运行中":
                current_running += 1
        return current_running


@api_user_bp.route("/api/announcements/active", methods=["GET"])
@login_required
def get_active_announcement():
    """获取当前用户应展示的公告（若无则返回announcement=null）"""
    try:
        user_id = int(current_user.id)
    except Exception:
        return jsonify({"announcement": None})

    announcement = database.get_active_announcement_for_user(user_id)
    if not announcement:
        return jsonify({"announcement": None})

    return jsonify(
        {
            "announcement": {
                "id": announcement.get("id"),
                "title": announcement.get("title", ""),
                "content": announcement.get("content", ""),
                "image_url": announcement.get("image_url") or "",
                "created_at": announcement.get("created_at"),
            }
        }
    )


@api_user_bp.route("/api/announcements/<int:announcement_id>/dismiss", methods=["POST"])
@login_required
def dismiss_announcement(announcement_id):
    """用户永久关闭某条公告（本次公告不再弹窗）"""
    try:
        user_id = int(current_user.id)
    except Exception:
        return jsonify({"error": "请先登录"}), 401

    announcement = database.get_announcement_by_id(announcement_id)
    if not announcement:
        return jsonify({"error": "公告不存在"}), 404

    database.dismiss_announcement_for_user(user_id, announcement_id)
    return jsonify({"success": True})


@api_user_bp.route("/api/feedback", methods=["POST"])
@login_required
def submit_feedback():
    """用户提交Bug反馈"""
    data = request.get_json()
    title = data.get("title", "").strip()
    description = data.get("description", "").strip()
    contact = data.get("contact", "").strip()

    if not title or not description:
        return jsonify({"error": "标题和描述不能为空"}), 400

    if len(title) > 100:
        return jsonify({"error": "标题不能超过100个字符"}), 400

    if len(description) > 2000:
        return jsonify({"error": "描述不能超过2000个字符"}), 400

    username = _get_current_username(fallback=f"用户{current_user.id}")

    feedback_id = database.create_bug_feedback(
        user_id=current_user.id,
        username=username,
        title=title,
        description=description,
        contact=contact,
    )

    return jsonify({"message": "反馈提交成功", "id": feedback_id})


@api_user_bp.route("/api/feedback", methods=["GET"])
@login_required
def get_my_feedbacks():
    """获取当前用户的反馈列表"""
    feedbacks = database.get_user_feedbacks(current_user.id)
    return jsonify(feedbacks)


@api_user_bp.route("/api/user/vip", methods=["GET"])
@login_required
def get_current_user_vip():
    """获取当前用户VIP信息"""
    vip_info = database.get_user_vip_info(current_user.id)
    vip_info["username"] = _get_current_username(fallback="Unknown")
    return jsonify(vip_info)


@api_user_bp.route("/api/user/password", methods=["POST"])
@login_required
def change_user_password():
    """用户修改自己的密码"""
    data = request.get_json()
    current_password = data.get("current_password")
    new_password = data.get("new_password")

    if not current_password or not new_password:
        return jsonify({"error": "请填写完整信息"}), 400

    is_valid, error_msg = validate_password(new_password)
    if not is_valid:
        return jsonify({"error": error_msg}), 400

    user, error_response = _get_current_user_or_404()
    if error_response:
        return error_response

    username = user.get("username", "")
    if not username or not database.verify_user(username, current_password):
        return jsonify({"error": "当前密码错误"}), 400

    if database.admin_reset_user_password(current_user.id, new_password):
        return jsonify({"success": True})
    return jsonify({"error": "密码更新失败"}), 500


@api_user_bp.route("/api/user/email", methods=["GET"])
@login_required
def get_user_email():
    """获取当前用户的邮箱信息"""
    user, error_response = _get_current_user_or_404()
    if error_response:
        return error_response

    return jsonify({"email": user.get("email", ""), "email_verified": user.get("email_verified", False)})


@api_user_bp.route("/api/user/kdocs", methods=["GET"])
@login_required
def get_user_kdocs_settings():
    """获取当前用户的金山文档设置"""
    settings = database.get_user_kdocs_settings(current_user.id) or {}
    cfg = database.get_system_config() or {}
    default_unit = (cfg.get("kdocs_default_unit") or "").strip() or "道县"
    kdocs_unit = (settings.get("kdocs_unit") or "").strip() or default_unit
    kdocs_auto_upload = 1 if int(settings.get("kdocs_auto_upload", 0) or 0) == 1 else 0
    return jsonify({"kdocs_unit": kdocs_unit, "kdocs_auto_upload": kdocs_auto_upload})


@api_user_bp.route("/api/user/kdocs", methods=["POST"])
@login_required
def update_user_kdocs_settings():
    """更新当前用户的金山文档设置"""
    data = request.get_json() or {}
    kdocs_unit = data.get("kdocs_unit")
    kdocs_auto_upload = data.get("kdocs_auto_upload")

    if kdocs_unit is not None:
        kdocs_unit = str(kdocs_unit or "").strip()
        if len(kdocs_unit) > 50:
            return jsonify({"error": "县区长度不能超过50"}), 400

    if kdocs_auto_upload is not None:
        kdocs_auto_upload, parse_error = _coerce_binary_flag(kdocs_auto_upload, field_label="自动上传开关")
        if parse_error:
            return jsonify({"error": parse_error}), 400

    if not database.update_user_kdocs_settings(
        current_user.id,
        kdocs_unit=kdocs_unit,
        kdocs_auto_upload=kdocs_auto_upload,
    ):
        return jsonify({"error": "更新失败"}), 400

    settings = database.get_user_kdocs_settings(current_user.id) or {}
    cfg = database.get_system_config() or {}
    default_unit = (cfg.get("kdocs_default_unit") or "").strip() or "道县"
    response_settings = {
        "kdocs_unit": (settings.get("kdocs_unit") or "").strip() or default_unit,
        "kdocs_auto_upload": 1 if int(settings.get("kdocs_auto_upload", 0) or 0) == 1 else 0,
    }
    return jsonify({"success": True, "settings": response_settings})


@api_user_bp.route("/api/user/bind-email", methods=["POST"])
@login_required
@require_ip_not_locked
def bind_user_email():
    """发送邮箱绑定验证邮件"""
    data = request.get_json() or {}
    email = data.get("email", "").strip().lower()

    if not email:
        return jsonify({"error": "请输入有效的邮箱地址"}), 400

    is_valid, error_msg = validate_email(email)
    if not is_valid:
        return jsonify({"error": error_msg}), 400

    allowed, error_msg, status_code = _check_bind_email_rate_limits(email)
    if not allowed:
        return jsonify({"error": error_msg}), status_code

    settings = email_service.get_email_settings()
    if not settings.get("enabled", False):
        return jsonify({"error": "邮件功能未启用，请联系管理员"}), 400

    existing_user = database.get_user_by_email(email)
    if existing_user and existing_user["id"] != current_user.id:
        return jsonify({"error": "该邮箱已被其他用户绑定"}), 400

    user, error_response = _get_current_user_or_404()
    if error_response:
        return error_response

    if user.get("email") == email and user.get("email_verified"):
        return jsonify({"error": "该邮箱已绑定并验证"}), 400

    result = email_service.send_bind_email_verification(user_id=current_user.id, email=email, username=user["username"])

    if result["success"]:
        return jsonify({"success": True, "message": "验证邮件已发送，请查收"})
    return jsonify({"error": result["error"]}), 500


@api_user_bp.route("/api/verify-bind-email/<token>")
def verify_bind_email(token):
    """验证邮箱绑定Token"""
    result = email_service.verify_bind_email_token(token, consume=False)

    if result:
        token_id = result["token_id"]
        user_id = result["user_id"]
        email = result["email"]

        if database.update_user_email(user_id, email, verified=True):
            if not email_service.consume_email_token(token_id):
                logger.warning(f"邮箱绑定成功但Token消费失败: user_id={user_id}")
            return _render_verify_bind_success(email)

        return _render_verify_bind_failed(title="绑定失败", error_message="邮箱绑定失败，请重试")

    return _render_verify_bind_failed(title="链接无效", error_message="验证链接已过期或无效，请重新发送验证邮件")


@api_user_bp.route("/api/user/unbind-email", methods=["POST"])
@login_required
def unbind_user_email():
    """解绑用户邮箱"""
    user, error_response = _get_current_user_or_404()
    if error_response:
        return error_response

    if not user.get("email"):
        return jsonify({"error": "当前未绑定邮箱"}), 400

    if database.update_user_email(current_user.id, None, verified=False):
        return jsonify({"success": True, "message": "邮箱已解绑"})
    return jsonify({"error": "解绑失败"}), 500


@api_user_bp.route("/api/user/email-notify", methods=["GET"])
@login_required
def get_user_email_notify():
    """获取用户邮件通知偏好"""
    enabled = database.get_user_email_notify(current_user.id)
    return jsonify({"enabled": enabled})


@api_user_bp.route("/api/user/email-notify", methods=["POST"])
@login_required
def update_user_email_notify():
    """更新用户邮件通知偏好"""
    data = request.get_json()
    enabled = data.get("enabled", True)

    if database.update_user_email_notify(current_user.id, enabled):
        return jsonify({"success": True})
    return jsonify({"error": "更新失败"}), 500


@api_user_bp.route("/api/user/passkeys", methods=["GET"])
@login_required
def list_user_passkeys():
    """获取当前用户绑定的 Passkey 设备列表。"""
    rows = database.list_passkeys("user", int(current_user.id))
    items = []
    for row in rows:
        credential_id = str(row.get("credential_id") or "")
        preview = ""
        if credential_id:
            preview = f"{credential_id[:8]}...{credential_id[-6:]}" if len(credential_id) > 16 else credential_id
        items.append(
            {
                "id": int(row.get("id")),
                "device_name": str(row.get("device_name") or ""),
                "credential_id_preview": preview,
                "created_at": row.get("created_at"),
                "last_used_at": row.get("last_used_at"),
                "transports": str(row.get("transports") or ""),
            }
        )
    return jsonify({"items": items, "limit": MAX_PASSKEYS_PER_OWNER})


@api_user_bp.route("/api/user/passkeys/register/options", methods=["POST"])
@login_required
def user_passkey_register_options():
    """当前登录用户创建 Passkey：下发 registration challenge。"""
    user, error_response = _get_current_user_or_404()
    if error_response:
        return error_response

    count = database.count_passkeys("user", int(current_user.id))
    if count >= MAX_PASSKEYS_PER_OWNER:
        return jsonify({"error": f"最多可绑定{MAX_PASSKEYS_PER_OWNER}台设备"}), 400

    data = request.get_json(silent=True) or {}
    device_name = normalize_device_name(data.get("device_name"))

    existing = database.list_passkeys("user", int(current_user.id))
    exclude_credential_ids = [str(item.get("credential_id") or "").strip() for item in existing if item.get("credential_id")]

    try:
        rp_id = get_rp_id(request)
        expected_origins = get_expected_origins(request)
    except Exception as e:
        logger.warning(f"[passkey] 用户注册 options 失败(user_id={current_user.id}): {e}")
        return jsonify({"error": "Passkey配置异常，请联系管理员"}), 500

    try:
        options = make_registration_options(
            rp_id=rp_id,
            rp_name="知识管理平台",
            user_name=str(user.get("username") or f"user-{current_user.id}"),
            user_display_name=str(user.get("username") or f"user-{current_user.id}"),
            user_id_bytes=f"user:{int(current_user.id)}".encode("utf-8"),
            exclude_credential_ids=exclude_credential_ids,
        )
    except Exception as e:
        logger.warning(f"[passkey] 用户注册 options 构建失败(user_id={current_user.id}): {e}")
        return jsonify({"error": "生成Passkey挑战失败"}), 500

    challenge = str(options.get("challenge") or "").strip()
    if not challenge:
        return jsonify({"error": "生成Passkey挑战失败"}), 500

    session[_USER_PASSKEY_REGISTER_SESSION_KEY] = {
        "user_id": int(current_user.id),
        "challenge": challenge,
        "rp_id": rp_id,
        "expected_origins": expected_origins,
        "device_name": device_name,
        "created_at": time.time(),
    }
    session.modified = True
    return jsonify({"publicKey": options, "limit": MAX_PASSKEYS_PER_OWNER})


@api_user_bp.route("/api/user/passkeys/register/verify", methods=["POST"])
@login_required
def user_passkey_register_verify():
    """当前登录用户创建 Passkey：校验 attestation 并落库。"""
    state = session.get(_USER_PASSKEY_REGISTER_SESSION_KEY) or {}
    if not state:
        return jsonify({"error": "Passkey挑战不存在或已过期，请重试"}), 400
    if int(state.get("user_id") or 0) != int(current_user.id):
        return jsonify({"error": "Passkey挑战与当前用户不匹配"}), 400
    if not is_challenge_valid(state.get("created_at")):
        session.pop(_USER_PASSKEY_REGISTER_SESSION_KEY, None)
        return jsonify({"error": "Passkey挑战已过期，请重试"}), 400

    data = request.get_json(silent=True) or {}
    credential = _parse_credential_payload(data)
    if not credential:
        return jsonify({"error": "Passkey参数缺失"}), 400

    count = database.count_passkeys("user", int(current_user.id))
    if count >= MAX_PASSKEYS_PER_OWNER:
        session.pop(_USER_PASSKEY_REGISTER_SESSION_KEY, None)
        return jsonify({"error": f"最多可绑定{MAX_PASSKEYS_PER_OWNER}台设备"}), 400

    try:
        verified = verify_registration(
            credential=credential,
            expected_challenge=str(state.get("challenge") or ""),
            expected_rp_id=str(state.get("rp_id") or ""),
            expected_origins=list(state.get("expected_origins") or []),
        )
    except Exception as e:
        logger.warning(f"[passkey] 用户注册验签失败(user_id={current_user.id}): {e}")
        return jsonify({"error": "Passkey验证失败，请重试"}), 400

    credential_id = encode_credential_id(verified.credential_id)
    public_key = encode_credential_id(verified.credential_public_key)
    transports = get_credential_transports(credential)
    device_name = normalize_device_name(data.get("device_name") if "device_name" in data else state.get("device_name"))
    aaguid = str(verified.aaguid or "")

    created_id = database.create_passkey(
        "user",
        int(current_user.id),
        credential_id=credential_id,
        public_key=public_key,
        sign_count=int(verified.sign_count or 0),
        device_name=device_name,
        transports=transports,
        aaguid=aaguid,
    )
    if not created_id:
        return jsonify({"error": "该Passkey已绑定，或保存失败"}), 400

    session.pop(_USER_PASSKEY_REGISTER_SESSION_KEY, None)
    return jsonify({"success": True, "id": int(created_id), "device_name": device_name})


@api_user_bp.route("/api/user/passkeys/<int:passkey_id>", methods=["DELETE"])
@login_required
def delete_user_passkey(passkey_id):
    """删除当前用户绑定的 Passkey 设备。"""
    ok = database.delete_passkey("user", int(current_user.id), int(passkey_id))
    if ok:
        return jsonify({"success": True})
    return jsonify({"error": "设备不存在或已删除"}), 404


@api_user_bp.route("/api/user/passkeys/client-error", methods=["POST"])
@login_required
def report_user_passkey_client_error():
    """上报浏览器端 Passkey 失败详情，便于排查兼容性问题。"""
    data = request.get_json(silent=True) or {}
    error_name = _truncate_text(data.get("name"), 120)
    error_message = _truncate_text(data.get("message"), 400)
    error_code = _truncate_text(data.get("code"), 120)
    ua = _truncate_text(data.get("user_agent") or request.headers.get("User-Agent", ""), 300)
    stage = _truncate_text(data.get("stage"), 80)
    source = _truncate_text(data.get("source"), 80)

    logger.warning(
        "[passkey][client-error][user] user_id=%s stage=%s source=%s name=%s code=%s message=%s ua=%s",
        current_user.id,
        stage or "-",
        source or "-",
        error_name or "-",
        error_code or "-",
        error_message or "-",
        ua or "-",
    )
    return jsonify({"success": True})


@api_user_bp.route("/api/run_stats", methods=["GET"])
@login_required
def get_run_stats():
    """获取当前用户的运行统计"""
    user_id = current_user.id

    stats = database.get_user_run_stats(user_id)

    current_running = _get_current_running_count(user_id)

    return jsonify(
        {
            "today_completed": stats.get("completed", 0),
            "current_running": current_running,
            "today_failed": stats.get("failed", 0),
            "today_items": stats.get("total_items", 0),
            "today_attachments": stats.get("total_attachments", 0),
        }
    )


@api_user_bp.route("/api/kdocs/status", methods=["GET"])
@login_required
def get_kdocs_status_for_user():
    """获取金山文档在线状态（用户端简化版）"""
    try:
        # 检查系统是否启用了金山文档功能
        cfg = database.get_system_config() or {}
        kdocs_enabled = int(cfg.get("kdocs_enabled") or 0)

        if not kdocs_enabled:
            return jsonify({"enabled": False, "online": False, "message": "未启用"})

        # 获取金山文档状态
        from services.kdocs_uploader import get_kdocs_uploader

        kdocs = get_kdocs_uploader()
        status = kdocs.get_status()

        login_required_flag = status.get("login_required", False)
        last_login_ok = status.get("last_login_ok")

        # 重启后首次查询时，状态可能还是 None，这里做一次轻量实时校验
        if last_login_ok is None:
            live_status = kdocs.refresh_login_status()
            if live_status.get("success"):
                logged_in = bool(live_status.get("logged_in"))
                login_required_flag = not logged_in
                last_login_ok = logged_in

        # 判断是否在线
        is_online = not login_required_flag and last_login_ok is True

        return jsonify({
            "enabled": True,
            "online": is_online,
            "message": "就绪" if is_online else "离线"
        })
    except Exception as e:
        logger.error(f"获取金山文档状态失败: {e}")
        return jsonify({"enabled": False, "online": False, "message": "获取失败"})
