"""
User account management — SQLite-backed user store with PBKDF2 password hashing.
"""
from __future__ import annotations

import hashlib
import os
import secrets
import sqlite3
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

ACCOUNTS_DB = Path(__file__).resolve().parent.parent.parent / "data" / "auth" / "accounts.db"
ROLES = ["admin", "trader", "viewer"]
ROLE_PERMISSIONS = {
    "admin": ["*"],  # all permissions
    "trader": ["trade:execute", "trade:view", "config:view", "config:edit_params", "logs:view"],
    "viewer": ["trade:view", "config:view", "logs:view"],
}


def _connect() -> sqlite3.Connection:
    ACCOUNTS_DB.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(str(ACCOUNTS_DB))
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA foreign_keys=ON")
    return conn


def init_db() -> None:
    conn = _connect()
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            email TEXT UNIQUE NOT NULL,
            password_hash TEXT NOT NULL,
            salt TEXT NOT NULL,
            role TEXT NOT NULL DEFAULT 'viewer',
            display_name TEXT NOT NULL DEFAULT '',
            is_active INTEGER NOT NULL DEFAULT 1,
            created_at TEXT NOT NULL,
            last_login_at TEXT
        );
        CREATE TABLE IF NOT EXISTS api_keys (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            key_hash TEXT UNIQUE NOT NULL,
            key_prefix TEXT NOT NULL,
            name TEXT NOT NULL DEFAULT '',
            scopes TEXT NOT NULL DEFAULT 'trade:view',
            created_at TEXT NOT NULL,
            last_used_at TEXT,
            expires_at TEXT,
            is_active INTEGER NOT NULL DEFAULT 1,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        );
        CREATE TABLE IF NOT EXISTS sessions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            token_hash TEXT UNIQUE NOT NULL,
            created_at TEXT NOT NULL,
            expires_at TEXT NOT NULL,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        );
        CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
        CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
        CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token_hash);
    """)
    conn.commit()
    conn.close()


# ─── Password hashing (PBKDF2-SHA256, 200k iterations) ─────────────

def _hash_password(password: str, salt: str | None = None) -> tuple[str, str]:
    if salt is None:
        salt = secrets.token_hex(16)
    dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 200_000)
    return dk.hex(), salt


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


# ─── User CRUD ─────────────────────────────────────────────────────

def create_user(email: str, password: str, role: str = "viewer",
                display_name: str = "") -> dict[str, Any]:
    if role not in ROLES:
        raise ValueError(f"Invalid role: {role}. Choose from: {ROLES}")

    pw_hash, salt = _hash_password(password)
    conn = _connect()
    try:
        conn.execute(
            "INSERT INTO users (email, password_hash, salt, role, display_name, created_at) "
            "VALUES (?, ?, ?, ?, ?, ?)",
            (email.lower().strip(), pw_hash, salt, role, display_name.strip(), _now()),
        )
        conn.commit()
        row = conn.execute("SELECT * FROM users WHERE email = ?", (email.lower().strip(),)).fetchone()
    except sqlite3.IntegrityError:
        conn.close()
        return {"ok": False, "error": "EMAIL_EXISTS", "message": "A user with this email already exists."}
    finally:
        conn.close()
    return {"ok": True, "user": _user_dict(dict(row))}


def authenticate(email: str, password: str) -> dict[str, Any]:
    conn = _connect()
    row = conn.execute("SELECT * FROM users WHERE email = ? AND is_active = 1",
                       (email.lower().strip(),)).fetchone()
    conn.close()
    if not row:
        return {"ok": False, "error": "INVALID_CREDENTIALS"}
    user = dict(row)
    pw_hash, _ = _hash_password(password, user["salt"])
    if not secrets.compare_digest(pw_hash, user["password_hash"]):
        return {"ok": False, "error": "INVALID_CREDENTIALS"}
    # Update last_login
    conn = _connect()
    conn.execute("UPDATE users SET last_login_at = ? WHERE id = ?", (_now(), user["id"]))
    conn.commit()
    conn.close()
    return {"ok": True, "user": _user_dict(user)}


def get_user(user_id: int) -> dict[str, Any] | None:
    conn = _connect()
    row = conn.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
    conn.close()
    return _user_dict(dict(row)) if row else None


def list_users() -> list[dict[str, Any]]:
    conn = _connect()
    rows = conn.execute("SELECT * FROM users ORDER BY created_at DESC").fetchall()
    conn.close()
    return [_user_dict(dict(r)) for r in rows]


def update_user_role(user_id: int, role: str) -> bool:
    if role not in ROLES:
        return False
    conn = _connect()
    conn.execute("UPDATE users SET role = ? WHERE id = ?", (role, user_id))
    conn.commit()
    conn.close()
    return True


def deactivate_user(user_id: int) -> bool:
    conn = _connect()
    conn.execute("UPDATE users SET is_active = 0 WHERE id = ?", (user_id,))
    # Also deactivate all API keys and sessions
    conn.execute("UPDATE api_keys SET is_active = 0 WHERE user_id = ?", (user_id,))
    conn.execute("DELETE FROM sessions WHERE user_id = ?", (user_id,))
    conn.commit()
    conn.close()
    return True


def _user_dict(u: dict[str, Any]) -> dict[str, Any]:
    return {
        "id": u["id"],
        "email": u["email"],
        "role": u["role"],
        "display_name": u.get("display_name", ""),
        "permissions": ROLE_PERMISSIONS.get(u["role"], []),
        "is_active": bool(u.get("is_active", 1)),
        "created_at": u.get("created_at", ""),
        "last_login_at": u.get("last_login_at", ""),
    }
