"""用户端许可客户端：对接 https://sq.bz121.com （crypto_monitor_web）。"""

from __future__ import annotations

import hashlib
import json
import os
import platform
import threading
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

from lib.paths import REPO_ROOT

DEFAULT_API_URL = "https://sq.bz121.com"
VALIDATE_INTERVAL_SEC = 3 * 24 * 3600  # 每 3 天
DEFAULT_OFFLINE_GRACE_HOURS = 72
STATE_FILENAME = "license_state.json"
LICENSE_ENV_NAME = "license.env"

_lock = threading.RLock()
_cached_device_id: str | None = None
_env_loaded = False


def _load_license_env() -> None:
    global _env_loaded
    if _env_loaded:
        return
    _env_loaded = True
    path = REPO_ROOT / LICENSE_ENV_NAME
    if not path.is_file():
        return
    try:
        text = path.read_text(encoding="utf-8")
    except OSError:
        return
    for line in text.splitlines():
        raw = line.strip()
        if not raw or raw.startswith("#") or "=" not in raw:
            continue
        key, value = raw.split("=", 1)
        key = key.strip().lstrip("\ufeff")
        if not key:
            continue
        val = value.strip().strip('"').strip("'")
        # 仅在未设置或为空时写入，避免空 LICENSE_CLIENT_KEY= 挡住 license.env
        if (os.environ.get(key) or "").strip():
            continue
        os.environ[key] = val


def api_base_url() -> str:
    _load_license_env()
    url = (os.getenv("LICENSE_API_URL") or DEFAULT_API_URL).strip().rstrip("/")
    return url or DEFAULT_API_URL


def client_key() -> str:
    _load_license_env()
    env_key = (os.getenv("LICENSE_CLIENT_KEY") or "").strip()
    if env_key:
        return env_key
    with _lock:
        st = _read_state()
    return (st.get("client_api_key") or "").strip()


def set_client_api_key(key: str) -> dict[str, Any]:
    """保存用户在授权页填写的客户密钥 / client token（写入 license_state.json）。"""
    key = (key or "").strip()
    if len(key) < 8:
        return {"ok": False, "message": "客户密钥太短"}
    with _lock:
        st = _read_state()
        st["client_api_key"] = key
        _write_state(st)
    os.environ["LICENSE_CLIENT_KEY"] = key
    return {"ok": True, "message": "客户密钥已保存"}


def offline_grace_hours() -> float:
    _load_license_env()
    raw = (os.getenv("LICENSE_OFFLINE_GRACE_HOURS") or str(DEFAULT_OFFLINE_GRACE_HOURS)).strip()
    try:
        return max(0.0, float(raw))
    except ValueError:
        return float(DEFAULT_OFFLINE_GRACE_HOURS)


def state_path() -> Path:
    _load_license_env()
    override = (os.getenv("LICENSE_STATE_PATH") or "").strip()
    if override:
        p = Path(override)
        if not p.is_absolute():
            p = REPO_ROOT / p
        p.parent.mkdir(parents=True, exist_ok=True)
        return p
    data = REPO_ROOT / "data"
    data.mkdir(parents=True, exist_ok=True)
    return data / STATE_FILENAME


def get_device_id() -> str:
    """稳定设备指纹，长度 16–64，供 redeem/validate 使用。"""
    global _cached_device_id
    if _cached_device_id:
        return _cached_device_id
    parts = [
        platform.node() or "",
        platform.system() or "",
        platform.machine() or "",
        hex(uuid.getnode()),
    ]
    try:
        machine_id = Path("/etc/machine-id")
        if machine_id.is_file():
            parts.append(machine_id.read_text(encoding="utf-8").strip())
    except OSError:
        pass
    try:
        win_id = Path(os.environ.get("SystemRoot", r"C:\Windows")) / "System32" / "drivers" / "etc"
        parts.append(str(win_id.resolve()))
    except OSError:
        pass
    digest = hashlib.sha256("|".join(parts).encode("utf-8", errors="ignore")).hexdigest()
    _cached_device_id = digest[:40]
    return _cached_device_id


def _read_state() -> dict[str, Any]:
    path = state_path()
    if not path.is_file():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return data if isinstance(data, dict) else {}
    except (OSError, json.JSONDecodeError):
        return {}


def _write_state(data: dict[str, Any]) -> None:
    path = state_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = path.with_suffix(".tmp")
    tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
    tmp.replace(path)


def _now_ts() -> float:
    return time.time()


def _parse_expires_at(value: str | None) -> float | None:
    if not value:
        return None
    s = value.strip()
    if not s:
        return None
    try:
        if s.endswith("Z"):
            s = s[:-1] + "+00:00"
        dt = datetime.fromisoformat(s)
        if dt.tzinfo is None:
            # 授权站按北京时间存，无 tz 时按 UTC+8
            from datetime import timedelta, timezone as _tz

            dt = dt.replace(tzinfo=_tz(timedelta(hours=8)))
        return dt.timestamp()
    except ValueError:
        return None


def _http_json(method: str, path: str, body: dict[str, Any]) -> dict[str, Any]:
    key = client_key()
    if not key:
        return {"ok": False, "message": "未配置客户密钥。请在软件授权页填写卖家提供的客户密钥（与激活码一起发放）。"}
    url = f"{api_base_url()}{path}"
    payload = json.dumps(body).encode("utf-8")
    req = Request(
        url,
        data=payload,
        method=method.upper(),
        headers={
            "Content-Type": "application/json",
            "X-Client-Key": key,
            "User-Agent": "crypto_monitor_user/license",
        },
    )
    try:
        with urlopen(req, timeout=20) as resp:
            raw = resp.read().decode("utf-8", errors="replace")
            data = json.loads(raw) if raw else {}
            return data if isinstance(data, dict) else {"ok": False, "message": "响应格式错误"}
    except HTTPError as e:
        try:
            detail = e.read().decode("utf-8", errors="replace")
        except Exception:
            detail = str(e)
        return {"ok": False, "message": f"HTTP {e.code}: {detail[:200]}", "network_error": True}
    except (URLError, TimeoutError, OSError) as e:
        return {"ok": False, "message": f"无法连接授权服务: {e}", "network_error": True}
    except json.JSONDecodeError:
        return {"ok": False, "message": "授权服务返回非 JSON", "network_error": True}


def redeem_code(code: str, client_api_key: str | None = None) -> dict[str, Any]:
    code = (code or "").strip().upper().replace(" ", "")
    if len(code) < 8:
        return {"ok": False, "message": "激活码无效"}
    if client_api_key is not None and str(client_api_key).strip():
        saved = set_client_api_key(str(client_api_key))
        if not saved.get("ok"):
            return saved
    if not client_key():
        return {
            "ok": False,
            "message": "请先填写客户密钥（与激活码一起由卖家提供；续费时可留空沿用本地已保存的密钥）",
        }
    device_id = get_device_id()
    result = _http_json("POST", "/v1/redeem", {"device_id": device_id, "code": code})
    if not result.get("ok"):
        return {
            "ok": False,
            "message": result.get("message") or "兑换失败",
            "network_error": bool(result.get("network_error")),
        }
    now = _now_ts()
    with _lock:
        state = _read_state()
        state.update(
            {
                "device_id": device_id,
                "subscription_id": result.get("subscription_id"),
                "plan": result.get("plan"),
                "expires_at": result.get("expires_at"),
                "last_ok_at": now,
                "last_validate_at": now,
                "last_reason": "",
            }
        )
        _write_state(state)
    return {
        "ok": True,
        "message": result.get("message") or "激活成功",
        "subscription_id": result.get("subscription_id"),
        "plan": result.get("plan"),
        "expires_at": result.get("expires_at"),
        "device_id": device_id,
    }


def validate_license(*, force: bool = False) -> dict[str, Any]:
    """联网校验；更新本地状态。force=True 忽略 3 天间隔。"""
    with _lock:
        state = _read_state()
    sub_id = (state.get("subscription_id") or "").strip()
    if not sub_id:
        return {"ok": True, "valid": False, "reason": "not_activated", "message": "尚未激活"}

    last_v = float(state.get("last_validate_at") or 0)
    if not force and last_v and (_now_ts() - last_v) < VALIDATE_INTERVAL_SEC:
        return get_license_status()

    device_id = get_device_id()
    result = _http_json(
        "POST",
        "/v1/validate",
        {"device_id": device_id, "subscription_id": sub_id},
    )
    now = _now_ts()
    if result.get("network_error"):
        # 断网：在宽限期内沿用本地到期日
        status = get_license_status(skip_remote=True)
        status["message"] = result.get("message") or "网络异常，使用本地宽限"
        status["network_error"] = True
        with _lock:
            st = _read_state()
            st["last_network_error_at"] = now
            st["last_reason"] = "network_error"
            _write_state(st)
        return status

    valid = bool(result.get("valid"))
    reason = (result.get("reason") or "").strip()
    with _lock:
        st = _read_state()
        st["device_id"] = device_id
        st["last_validate_at"] = now
        if valid:
            st["last_ok_at"] = now
            st["last_reason"] = ""
            if result.get("expires_at"):
                st["expires_at"] = result.get("expires_at")
            if result.get("plan"):
                st["plan"] = result.get("plan")
        else:
            st["last_reason"] = reason or "invalid"
            if reason in ("expired", "device_revoked", "subscription_not_found", "subscription_inactive"):
                # 明确失效：保留字段便于页面展示，但 valid=False
                pass
        _write_state(st)

    return {
        "ok": True,
        "valid": valid,
        "reason": reason if not valid else "",
        "expires_at": result.get("expires_at") or state.get("expires_at"),
        "plan": result.get("plan") or state.get("plan"),
        "device_id": device_id,
        "subscription_id": sub_id,
        "message": "有效" if valid else (reason or "无效"),
    }


def get_license_status(*, skip_remote: bool = False) -> dict[str, Any]:
    """本地状态（可触发到期判断）；不主动联网除非 skip_remote=False 且到期需刷新。"""
    with _lock:
        state = _read_state()
    device_id = get_device_id()
    key = client_key()
    key_mask = (key[:4] + "…" + key[-4:]) if len(key) >= 12 else (("已配置" if key else ""))
    sub_id = (state.get("subscription_id") or "").strip()
    if not sub_id:
        return {
            "ok": True,
            "valid": False,
            "reason": "not_activated",
            "message": "尚未激活",
            "device_id": device_id,
            "api_url": api_base_url(),
            "has_client_key": bool(key),
            "client_key_masked": key_mask,
        }

    exp_ts = _parse_expires_at(state.get("expires_at"))
    now = _now_ts()
    if exp_ts is not None and now > exp_ts:
        return {
            "ok": True,
            "valid": False,
            "reason": "expired",
            "message": "许可已过期",
            "expires_at": state.get("expires_at"),
            "plan": state.get("plan"),
            "device_id": device_id,
            "subscription_id": sub_id,
            "api_url": api_base_url(),
            "has_client_key": bool(key),
            "client_key_masked": key_mask,
        }

    reason = (state.get("last_reason") or "").strip()
    if reason in ("expired", "device_revoked", "subscription_not_found", "subscription_inactive"):
        return {
            "ok": True,
            "valid": False,
            "reason": reason,
            "message": reason,
            "expires_at": state.get("expires_at"),
            "plan": state.get("plan"),
            "device_id": device_id,
            "subscription_id": sub_id,
            "api_url": api_base_url(),
            "has_client_key": bool(key),
            "client_key_masked": key_mask,
        }

    last_ok = float(state.get("last_ok_at") or 0)
    grace = offline_grace_hours() * 3600
    if last_ok and grace > 0 and (now - last_ok) > grace:
        # 超过离线宽限且长时间未成功校验
        last_v = float(state.get("last_validate_at") or 0)
        if last_v and (now - last_v) >= VALIDATE_INTERVAL_SEC and not skip_remote:
            return validate_license(force=True)
        if (now - last_ok) > grace:
            return {
                "ok": True,
                "valid": False,
                "reason": "offline_grace_exceeded",
                "message": "超过离线宽限期，请联网校验",
                "expires_at": state.get("expires_at"),
                "plan": state.get("plan"),
                "device_id": device_id,
                "subscription_id": sub_id,
                "api_url": api_base_url(),
                "has_client_key": bool(key),
                "client_key_masked": key_mask,
            }

    return {
        "ok": True,
        "valid": True,
        "reason": "",
        "message": "已授权",
        "expires_at": state.get("expires_at"),
        "plan": state.get("plan"),
        "device_id": device_id,
        "subscription_id": sub_id,
        "last_ok_at": last_ok or None,
        "api_url": api_base_url(),
        "has_client_key": bool(key),
        "client_key_masked": key_mask,
    }


def is_license_valid() -> bool:
    status = get_license_status()
    if status.get("valid"):
        # 到期需周期性联网
        last_v = 0.0
        with _lock:
            st = _read_state()
            last_v = float(st.get("last_validate_at") or 0)
        if last_v and (_now_ts() - last_v) >= VALIDATE_INTERVAL_SEC:
            status = validate_license(force=True)
        return bool(status.get("valid"))
    # 未激活或已失效：尝试若有 subscription 则强制校验一次（避免本地过期标志陈旧）
    with _lock:
        st = _read_state()
    if (st.get("subscription_id") or "").strip() and status.get("reason") not in (
        "not_activated",
        "expired",
        "device_revoked",
    ):
        status = validate_license(force=True)
        return bool(status.get("valid"))
    return False


def ensure_license_or_raise() -> None:
    if not is_license_valid():
        raise RuntimeError("license_required")
