from __future__ import annotations

import base64
import hashlib
import hmac
import json
import os
import time


HASH_NAME = "sha256"
HASH_ITERATIONS = 260_000


class TokenError(ValueError):
    pass


def hash_password(password: str) -> str:
    """使用 PBKDF2 保存密码摘要，数据库永远不存明文密码。"""
    salt = os.urandom(16)
    digest = hashlib.pbkdf2_hmac(HASH_NAME, password.encode("utf-8"), salt, HASH_ITERATIONS)
    return "$".join(
        [
            "pbkdf2_sha256",
            str(HASH_ITERATIONS),
            _b64encode(salt),
            _b64encode(digest),
        ]
    )


def verify_password(password: str, password_hash: str) -> bool:
    """用常量时间比较校验密码，降低时序侧信道风险。"""
    try:
        algorithm, iterations_text, salt_text, digest_text = password_hash.split("$", 3)
    except ValueError:
        return False
    if algorithm != "pbkdf2_sha256":
        return False
    salt = _b64decode(salt_text)
    expected = _b64decode(digest_text)
    actual = hashlib.pbkdf2_hmac(
        HASH_NAME,
        password.encode("utf-8"),
        salt,
        int(iterations_text),
    )
    return hmac.compare_digest(actual, expected)


def create_access_token(
    *,
    user_id: int,
    username: str,
    role: str,
    secret_key: str,
    expires_minutes: int,
) -> str:
    """生成轻量 HMAC token，payload 明文可读但不可被客户端篡改。"""
    payload = {
        "sub": str(user_id),
        "username": username,
        "role": role,
        "exp": int(time.time()) + expires_minutes * 60,
    }
    payload_text = _b64encode(json.dumps(payload, separators=(",", ":")).encode("utf-8"))
    signature = _sign(payload_text, secret_key)
    return f"{payload_text}.{signature}"


def decode_access_token(token: str, secret_key: str) -> dict:
    """校验 token 签名和过期时间，失败时抛出 TokenError。"""
    try:
        payload_text, signature = token.split(".", 1)
    except ValueError as exc:
        raise TokenError("无效 token") from exc
    expected_signature = _sign(payload_text, secret_key)
    if not hmac.compare_digest(signature, expected_signature):
        raise TokenError("无效 token 签名")
    payload = json.loads(_b64decode(payload_text))
    if int(payload.get("exp", 0)) < int(time.time()):
        raise TokenError("登录已过期")
    return payload


def _sign(payload_text: str, secret_key: str) -> str:
    digest = hmac.new(secret_key.encode("utf-8"), payload_text.encode("utf-8"), hashlib.sha256).digest()
    return _b64encode(digest)


def _b64encode(value: bytes) -> str:
    return base64.urlsafe_b64encode(value).rstrip(b"=").decode("ascii")


def _b64decode(value: str) -> bytes:
    padding = "=" * (-len(value) % 4)
    return base64.urlsafe_b64decode(value + padding)
