"""sqlite schema + 连接 + 基础工具。

设计：
- 所有身份和凭证以 hash 形式入库（phone / session_token / 验证码）
- WAL 模式（并发读写 + 崩溃恢复）
- tags 字段 pipe 包裹格式 `|a|b|c|`，LIKE 查包含
- exposure_usage.remaining > 0 等于"在付费池"

5 张表：
- users              身份锚点（phone_hash 唯一）
- profiles           可展示字段
- exposure_orders    买曝光的历史流水（永久）
- exposure_usage     剩余曝光次数（实时跳动）
- auth_requests      短信验证码临时存储
- sessions           登录态（token_hash → user_id）
"""

from __future__ import annotations

import hashlib
import hmac
import os
import sqlite3
import time
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator

import ulid as ulid_lib  # type: ignore

DEFAULT_DB_PATH = Path(
    os.environ.get("XIANGQIN_DB_PATH", "/opt/xiangqin/data/xiangqin.db")
)
PHONE_SALT = os.environ.get("XIANGQIN_PHONE_SALT", "dev-salt-not-for-prod").encode()

SCHEMA = """
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA synchronous = NORMAL;

CREATE TABLE IF NOT EXISTS users (
    id          TEXT PRIMARY KEY,
    phone_hash  TEXT UNIQUE NOT NULL,
    created_at  INTEGER NOT NULL,
    deleted_at  INTEGER
);

CREATE TABLE IF NOT EXISTS profiles (
    user_id     TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
    gender      TEXT CHECK(gender IN ('f','m','nb')),
    age         INTEGER CHECK(age IS NULL OR (age >= 18 AND age <= 99)),
    city        TEXT,
    tags        TEXT,
    bio         TEXT,
    updated_at  INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_profiles_gender_city_age
    ON profiles(gender, city, age);

CREATE TABLE IF NOT EXISTS exposure_orders (
    id              TEXT PRIMARY KEY,
    user_id         TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    count           INTEGER NOT NULL CHECK(count > 0),
    amount_cents    INTEGER NOT NULL CHECK(amount_cents >= 0),
    mock            INTEGER NOT NULL CHECK(mock IN (0,1)),
    paid            INTEGER NOT NULL CHECK(paid IN (0,1)),
    created_at      INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_orders_user ON exposure_orders(user_id, created_at DESC);

CREATE TABLE IF NOT EXISTS exposure_usage (
    user_id             TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
    remaining           INTEGER NOT NULL DEFAULT 0 CHECK(remaining >= 0),
    total_purchased     INTEGER NOT NULL DEFAULT 0,
    last_consumed_at    INTEGER
);

CREATE INDEX IF NOT EXISTS idx_usage_remaining ON exposure_usage(remaining)
    WHERE remaining > 0;

CREATE TABLE IF NOT EXISTS auth_requests (
    request_id      TEXT PRIMARY KEY,
    phone_hash      TEXT NOT NULL,
    code_hash       TEXT NOT NULL,
    expires_at      INTEGER NOT NULL,
    used            INTEGER NOT NULL DEFAULT 0 CHECK(used IN (0,1))
);

CREATE INDEX IF NOT EXISTS idx_auth_expires ON auth_requests(expires_at);

CREATE TABLE IF NOT EXISTS sessions (
    token_hash      TEXT PRIMARY KEY,
    user_id         TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    created_at      INTEGER NOT NULL,
    last_seen_at    INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
"""


def hash_phone(phone: str) -> str:
    """HMAC-SHA256(phone) → hex。SALT 走 env var。"""
    return hmac.new(PHONE_SALT, phone.encode(), hashlib.sha256).hexdigest()


def hash_code(code: str) -> str:
    """验证码 sha256（不加盐，验证码本身就是短期随机）。"""
    return hashlib.sha256(code.encode()).hexdigest()


def hash_token(token: str) -> str:
    """session_token sha256。"""
    return hashlib.sha256(token.encode()).hexdigest()


def new_id() -> str:
    """ULID 字符串，时序 + 随机。"""
    return str(ulid_lib.new())


def now_ts() -> int:
    return int(time.time())


def _resolve_path(path: Path | None) -> Path:
    """运行时动态查 DEFAULT_DB_PATH（测试可覆写 module attr）。"""
    if path is not None:
        return path
    return DEFAULT_DB_PATH


def init_db(path: Path | None = None) -> None:
    """建目录 + 执行 schema（幂等）。"""
    path = _resolve_path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(path) as conn:
        conn.executescript(SCHEMA)


@contextmanager
def get_conn(path: Path | None = None) -> Iterator[sqlite3.Connection]:
    """Context manager：返回 Row factory + foreign_keys 已开的连接。"""
    path = _resolve_path(path)
    conn = sqlite3.connect(path)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    try:
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()


def mask_phone(phone: str) -> str:
    """138****1111 —— 脱敏，永远不要输出完整手机号。"""
    if len(phone) < 7:
        return "***"
    return f"{phone[:3]}****{phone[-4:]}"
