# -*- coding: utf-8 -*-
"""
幸福工厂服务器管理器 - NAS 端激活验证服务
部署到飞牛 NAS，提供激活码验证 + 单设备在线限制

启动方式:
  pip install flask gunicorn
  gunicorn -w 1 -b 0.0.0.0:5050 nas_api:app

  或使用开发服务器:
  python nas_api.py

数据文件:
  activation_codes.json  - 激活码与设备绑定数据（自动生成）

环境变量（可选）:
  NAS_PORT        监听端口（默认 5050）
  NAS_DATA_DIR    数据目录（默认当前目录）
"""
import os
import json
import time
import hashlib
import threading
from datetime import datetime
from flask import Flask, request, jsonify

app = Flask(__name__)

# ==================== 配置 ====================
DATA_DIR = os.environ.get("NAS_DATA_DIR", os.path.dirname(os.path.abspath(__file__)))
os.makedirs(DATA_DIR, exist_ok=True)
CODES_FILE = os.path.join(DATA_DIR, "activation_codes.json")
DEVICE_TIMEOUT = 600          # 设备心跳超时（秒），超时视为离线
HEARTBEAT_INTERVAL = 180      # 客户端心跳间隔（秒），需与客户端匹配

# 文件锁
_lock = threading.Lock()


# ==================== 数据持久化 ====================
def _load_data():
    """加载激活码数据"""
    if os.path.exists(CODES_FILE):
        try:
            with open(CODES_FILE, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            pass
    return {"codes": {}}


def _save_data(data):
    """保存激活码数据"""
    with open(CODES_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)


def _generate_token():
    """生成随机 token"""
    import secrets
    return secrets.token_hex(32)


def _now_str():
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


# ==================== 激活码管理命令 ====================
def cmd_add_code(code, max_devices=1, note=""):
    """
    添加激活码（命令行调用）
    用法: python nas_api.py add <激活码> [最大设备数] [备注]
    """
    with _lock:
        data = _load_data()
        data["codes"][code] = {
            "max_devices": max_devices,
            "devices": {},
            "note": note,
            "created_at": _now_str(),
            "enabled": True,
        }
        _save_data(data)
    print(f"[OK] 激活码已添加: {code} (最大设备数: {max_devices})")


def cmd_list_codes():
    """列出所有激活码"""
    with _lock:
        data = _load_data()
    codes = data.get("codes", {})
    if not codes:
        print("暂无激活码")
        return
    print(f"{'激活码':<24} {'状态':<6} {'设备数':<8} {'最大':<6} {'备注':<20} {'创建时间'}")
    print("-" * 90)
    for code, info in codes.items():
        devices = info.get("devices", {})
        # 清理过期设备
        active = sum(1 for d in devices.values()
                     if time.time() - d.get("last_seen", 0) < DEVICE_TIMEOUT)
        status = "启用" if info.get("enabled", True) else "禁用"
        print(f"{code:<24} {status:<6} {active:<8} {info.get('max_devices', 1):<6} "
              f"{info.get('note', ''):<20} {info.get('created_at', '')}")
        for did, dinfo in devices.items():
            online = "在线" if time.time() - dinfo.get("last_seen", 0) < DEVICE_TIMEOUT else "离线"
            print(f"  └─ {did}  {online}  最后心跳: {dinfo.get('last_seen_str', '?')}")


def cmd_remove_code(code):
    """删除激活码"""
    with _lock:
        data = _load_data()
        if code in data["codes"]:
            del data["codes"][code]
            _save_data(data)
            print(f"[OK] 激活码已删除: {code}")
        else:
            print(f"[ERR] 激活码不存在: {code}")


# ==================== API 端点 ====================

@app.route("/api/activate", methods=["POST"])
def api_activate():
    """
    激活码验证 + 设备绑定

    请求: {"code": "XXXX-XXXX-XXXX", "device_id": "abcdef1234567890"}
    成功: {"success": true, "token": "hex64..."}
    失败: {"success": false, "message": "错误原因"}
    """
    body = request.get_json(force=True, silent=True) or {}
    code = (body.get("code") or "").strip()
    device_id = (body.get("device_id") or "").strip()

    if not code or not device_id:
        return jsonify({"success": False, "message": "缺少 code 或 device_id"}), 400

    with _lock:
        data = _load_data()
        code_info = data["codes"].get(code)

        # 验证激活码
        if not code_info:
            return jsonify({"success": False, "message": "激活码不存在"}), 401
        if not code_info.get("enabled", True):
            return jsonify({"success": False, "message": "激活码已禁用"}), 403

        devices = code_info.get("devices", {})
        max_dev = code_info.get("max_devices", 1)

        # 检查设备是否已绑定
        if device_id in devices:
            # 已有设备，更新心跳并返回 token
            token = devices[device_id].get("token", _generate_token())
            devices[device_id]["last_seen"] = time.time()
            devices[device_id]["last_seen_str"] = _now_str()
            devices[device_id]["token"] = token
            code_info["devices"] = devices
            data["codes"][code] = code_info
            _save_data(data)
            return jsonify({"success": True, "token": token})

        # 清理离线设备
        now = time.time()
        online_devices = {
            did: dinfo for did, dinfo in devices.items()
            if now - dinfo.get("last_seen", 0) < DEVICE_TIMEOUT
        }

        # 检查设备数限制
        if len(online_devices) >= max_dev:
            return jsonify({
                "success": False,
                "message": f"激活码已达最大设备数 ({max_dev})，其他设备在线中"
            }), 409

        # 绑定新设备
        token = _generate_token()
        devices[device_id] = {
            "token": token,
            "last_seen": now,
            "last_seen_str": _now_str(),
            "ip": request.remote_addr or "",
        }
        code_info["devices"] = devices
        data["codes"][code] = code_info
        _save_data(data)

    _log(f"激活成功: code={code}, device={device_id}, ip={request.remote_addr}")
    return jsonify({"success": True, "token": token})


@app.route("/api/status", methods=["GET"])
def api_status():
    """
    查询激活状态（心跳 + 检查单设备在线）

    请求: GET /api/status?device_id=xxx  Header: Authorization: Bearer <token>
    成功: {"valid": true, "device_count": 1}
    失败: {"valid": false, "message": "原因"}
    """
    device_id = request.args.get("device_id", "").strip()
    auth = request.headers.get("Authorization", "")
    token = auth.replace("Bearer ", "") if auth.startswith("Bearer ") else ""

    if not device_id or not token:
        return jsonify({"valid": False, "message": "缺少 device_id 或 token"}), 400

    with _lock:
        data = _load_data()
        for code, code_info in data["codes"].items():
            devices = code_info.get("devices", {})
            if device_id not in devices:
                continue
            dev = devices[device_id]
            if dev.get("token") != token:
                continue

            # 更新心跳
            dev["last_seen"] = time.time()
            dev["last_seen_str"] = _now_str()
            dev["ip"] = request.remote_addr or ""

            # 统计在线设备数
            now = time.time()
            online_count = sum(
                1 for d in devices.values()
                if now - d.get("last_seen", 0) < DEVICE_TIMEOUT
            )

            _save_data(data)
            return jsonify({"valid": True, "device_count": online_count})

    return jsonify({"valid": False, "message": "设备未激活或 token 无效"}), 401


@app.route("/api/deactivate", methods=["POST"])
def api_deactivate():
    """
    解除设备绑定

    请求: {"device_id": "xxx"}  Header: Authorization: Bearer <token>
    返回: {"success": true}
    """
    body = request.get_json(force=True, silent=True) or {}
    device_id = (body.get("device_id") or "").strip()
    auth = request.headers.get("Authorization", "")
    token = auth.replace("Bearer ", "") if auth.startswith("Bearer ") else ""

    if not device_id:
        return jsonify({"success": False, "message": "缺少 device_id"}), 400

    with _lock:
        data = _load_data()
        for code, code_info in data["codes"].items():
            devices = code_info.get("devices", {})
            if device_id in devices:
                dev = devices[device_id]
                if token and dev.get("token") != token:
                    continue
                del devices[device_id]
                code_info["devices"] = devices
                data["codes"][code] = code_info
                _save_data(data)
                _log(f"解除激活: code={code}, device={device_id}")
                return jsonify({"success": True})

    return jsonify({"success": False, "message": "设备未找到"}), 404


@app.route("/api/verify", methods=["GET"])
def api_verify():
    """
    验证 token 有效性（兼容旧接口）

    请求: GET /api/verify  Header: Authorization: Bearer <token>
    返回: {"valid": true, "activated": true}
    """
    auth = request.headers.get("Authorization", "")
    token = auth.replace("Bearer ", "") if auth.startswith("Bearer ") else ""
    if not token:
        return jsonify({"valid": False})

    with _lock:
        data = _load_data()
        for code_info in data["codes"].values():
            for dev in code_info.get("devices", {}).values():
                if dev.get("token") == token:
                    return jsonify({"valid": True, "activated": True})

    return jsonify({"valid": False}), 401


@app.route("/api/health", methods=["GET"])
def api_health():
    """健康检查"""
    return jsonify({"status": "ok", "time": _now_str()})


# ==================== 管理后台（简单 Web UI） ====================

@app.route("/")
def admin_page():
    """简单的管理后台页面"""
    with _lock:
        data = _load_data()
    codes = data.get("codes", {})
    rows = ""
    for code, info in codes.items():
        devices = info.get("devices", {})
        now = time.time()
        active = sum(1 for d in devices.values() if now - d.get("last_seen", 0) < DEVICE_TIMEOUT)
        status = "✅ 启用" if info.get("enabled", True) else "❌ 禁用"
        device_rows = ""
        for did, dinfo in devices.items():
            online = "🟢 在线" if now - dinfo.get("last_seen", 0) < DEVICE_TIMEOUT else "⚫ 离线"
            device_rows += f"<tr><td>{did}</td><td>{online}</td><td>{dinfo.get('ip','')}</td><td>{dinfo.get('last_seen_str','')}</td></tr>"
        rows += f"""
        <tr style="background:#f8f9fa"><td><b>{code}</b></td><td>{status}</td>
        <td>{active}/{info.get('max_devices',1)}</td><td>{info.get('note','')}</td>
        <td>{info.get('created_at','')}</td></tr>
        {device_rows}
        """
    return f"""<!DOCTYPE html><html><head><meta charset="utf-8">
    <title>激活码管理</title>
    <style>body{{font-family:sans-serif;padding:20px;max-width:900px;margin:0 auto}}
    table{{width:100%;border-collapse:collapse;margin:16px 0}}
    td,th{{border:1px solid #ddd;padding:8px;text-align:left;font-size:14px}}
    th{{background:#4a90d9;color:#fff}}h1{{color:#333}}</style></head>
    <body><h1>🏭 激活码管理后台</h1>
    <p>共 {len(codes)} 个激活码 | 设备超时: {DEVICE_TIMEOUT}秒</p>
    <table><tr><th>激活码</th><th>状态</th><th>设备</th><th>备注</th><th>创建时间</th></tr>
    {rows}</table>
    <p style="color:#999;font-size:12px">数据文件: {CODES_FILE}</p></body></html>"""


# ==================== 日志 ====================
def _log(msg):
    print(f"[{datetime.now().strftime('%H:%M:%S')}] {msg}")


# ==================== 启动入口 ====================
if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1:
        cmd = sys.argv[1]
        if cmd == "add" and len(sys.argv) >= 3:
            cmd_add_code(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 1,
                         sys.argv[4] if len(sys.argv) > 4 else "")
        elif cmd == "list":
            cmd_list_codes()
        elif cmd == "remove" and len(sys.argv) >= 3:
            cmd_remove_code(sys.argv[2])
        else:
            print("用法:")
            print("  python nas_api.py                          # 启动服务")
            print("  python nas_api.py add <激活码> [设备数] [备注]  # 添加激活码")
            print("  python nas_api.py list                     # 列出激活码")
            print("  python nas_api.py remove <激活码>           # 删除激活码")
    else:
        port = int(os.environ.get("NAS_PORT", 5050))
        _log(f"启动激活验证服务，端口 {port}")
        app.run(host="0.0.0.0", port=port, debug=False)

