"""
Brynq Human Authentication — Signup, Login, JWT Tokens.

SQLite-backed user store with bcrypt password hashing and JWT session tokens.
Designed for the brynq.ai web frontend — humans who manage AI workforces.

Usage:
    from brynq.auth import AuthManager
    auth = AuthManager()
    user = auth.signup("alice@example.com", "Alice", "securepassword")
    token = auth.login("alice@example.com", "securepassword")
    identity = auth.verify_token(token["access_token"])
"""

import hashlib
import hmac
import json
import os
import secrets
import sqlite3
import time
import uuid
from pathlib import Path
from typing import Optional, Dict, Any

# ── Configuration ────────────────────────────────────────────────────────

AUTH_DIR = Path("state/broker/auth")
DB_PATH = AUTH_DIR / "users.db"
JWT_SECRET = os.getenv("BRYNQ_JWT_SECRET", "")
JWT_EXPIRY_HOURS = int(os.getenv("BRYNQ_JWT_EXPIRY_HOURS", "24"))

# Admin identity from CEO config
ADMIN_EMAIL = "grewal@duck.com"


def _ensure_auth_dir():
    AUTH_DIR.mkdir(parents=True, exist_ok=True)


def _get_jwt_secret() -> str:
    """Get or generate JWT signing secret. Persists to disk for consistency."""
    global JWT_SECRET
    if JWT_SECRET:
        return JWT_SECRET
    secret_path = AUTH_DIR / ".jwt_secret"
    if secret_path.exists():
        JWT_SECRET = secret_path.read_text("utf-8").strip()
        return JWT_SECRET
    _ensure_auth_dir()
    JWT_SECRET = secrets.token_hex(32)
    secret_path.write_text(JWT_SECRET, encoding="utf-8")
    return JWT_SECRET


# ── Password Hashing (PBKDF2 — no external deps) ────────────────────────


def _hash_password(password: str, salt: Optional[bytes] = None) -> tuple[str, str]:
    """Hash password with PBKDF2-SHA256. Returns (hash_hex, salt_hex)."""
    if salt is None:
        salt = os.urandom(16)
    dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 100_000)
    return dk.hex(), salt.hex()


def _verify_password(password: str, stored_hash: str, stored_salt: str) -> bool:
    """Verify a password against stored hash and salt."""
    salt = bytes.fromhex(stored_salt)
    dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 100_000)
    return hmac.compare_digest(dk.hex(), stored_hash)


# ── JWT (minimal, no PyJWT dependency) ───────────────────────────────────

import base64


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


def _b64url_decode(s: str) -> bytes:
    padding = 4 - len(s) % 4
    if padding != 4:
        s += "=" * padding
    return base64.urlsafe_b64decode(s)


def _create_jwt(payload: dict, secret: str) -> str:
    """Create a minimal JWT (HS256)."""
    header = {"alg": "HS256", "typ": "JWT"}
    h = _b64url_encode(json.dumps(header).encode())
    p = _b64url_encode(json.dumps(payload, default=str).encode())
    sig_input = f"{h}.{p}".encode()
    sig = hmac.new(secret.encode(), sig_input, hashlib.sha256).digest()
    s = _b64url_encode(sig)
    return f"{h}.{p}.{s}"


def _verify_jwt(token: str, secret: str) -> Optional[dict]:
    """Verify and decode a JWT. Returns payload or None."""
    try:
        parts = token.split(".")
        if len(parts) != 3:
            return None
        h, p, s = parts
        sig_input = f"{h}.{p}".encode()
        expected_sig = hmac.new(secret.encode(), sig_input, hashlib.sha256).digest()
        actual_sig = _b64url_decode(s)
        if not hmac.compare_digest(expected_sig, actual_sig):
            return None
        payload = json.loads(_b64url_decode(p))
        if payload.get("exp", float("inf")) < time.time():
            return None
        return payload
    except (ValueError, json.JSONDecodeError, KeyError):
        return None


# ── SQLite User Store ────────────────────────────────────────────────────


class AuthManager:
    def __init__(self, db_path: Optional[str] = None):
        _ensure_auth_dir()
        self.db_path = db_path or str(DB_PATH)
        self._init_db()

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        return conn

    def _init_db(self):
        conn = self._connect()
        conn.execute("""
            CREATE TABLE IF NOT EXISTS users (
                user_id TEXT PRIMARY KEY,
                email TEXT UNIQUE NOT NULL,
                name TEXT NOT NULL,
                password_hash TEXT NOT NULL,
                password_salt TEXT NOT NULL,
                role TEXT DEFAULT 'user',
                tier TEXT DEFAULT 'free',
                status TEXT DEFAULT 'active',
                created_at REAL NOT NULL,
                last_login REAL
            )
        """)
        conn.execute("""
            CREATE TABLE IF NOT EXISTS sessions (
                token_id TEXT PRIMARY KEY,
                user_id TEXT NOT NULL,
                created_at REAL NOT NULL,
                expires_at REAL NOT NULL,
                revoked INTEGER DEFAULT 0,
                FOREIGN KEY (user_id) REFERENCES users(user_id)
            )
        """)
        conn.commit()
        conn.close()

        # Ensure admin user exists
        self._ensure_admin()

    def _ensure_admin(self):
        """Create the founder/CEO account if it doesn't exist."""
        conn = self._connect()
        row = conn.execute(
            "SELECT user_id FROM users WHERE email = ?", (ADMIN_EMAIL,)
        ).fetchone()
        if not row:
            pw_hash, pw_salt = _hash_password("admin")  # Default — CEO should change
            conn.execute(
                "INSERT INTO users (user_id, email, name, password_hash, password_salt, role, tier, created_at) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
                ("founder-001", ADMIN_EMAIL, "Gurjeet Grewal", pw_hash, pw_salt, "ceo", "enterprise", time.time()),
            )
            conn.commit()
        conn.close()

    # ── Public API ───────────────────────────────────────────────────

    def signup(self, email: str, name: str, password: str) -> Dict[str, Any]:
        """Register a new human user. Returns user info or error."""
        email = email.strip().lower()
        if not email or "@" not in email:
            return {"ok": False, "error": "Invalid email address"}
        if len(password) < 8:
            return {"ok": False, "error": "Password must be at least 8 characters"}
        if not name.strip():
            return {"ok": False, "error": "Name is required"}

        pw_hash, pw_salt = _hash_password(password)
        user_id = uuid.uuid4().hex[:12]

        conn = self._connect()
        try:
            conn.execute(
                "INSERT INTO users (user_id, email, name, password_hash, password_salt, created_at) "
                "VALUES (?, ?, ?, ?, ?, ?)",
                (user_id, email, name.strip(), pw_hash, pw_salt, time.time()),
            )
            conn.commit()
            return {
                "ok": True,
                "user_id": user_id,
                "email": email,
                "name": name.strip(),
                "tier": "free",
            }
        except sqlite3.IntegrityError:
            return {"ok": False, "error": "Email already registered"}
        finally:
            conn.close()

    def login(self, email: str, password: str) -> Dict[str, Any]:
        """Authenticate and return a JWT access token."""
        email = email.strip().lower()
        conn = self._connect()
        row = conn.execute(
            "SELECT * FROM users WHERE email = ? AND status = 'active'", (email,)
        ).fetchone()

        if not row:
            conn.close()
            return {"ok": False, "error": "Invalid email or password"}

        if not _verify_password(password, row["password_hash"], row["password_salt"]):
            conn.close()
            return {"ok": False, "error": "Invalid email or password"}

        # Update last login
        conn.execute("UPDATE users SET last_login = ? WHERE user_id = ?", (time.time(), row["user_id"]))

        # Create JWT
        secret = _get_jwt_secret()
        exp = time.time() + (JWT_EXPIRY_HOURS * 3600)
        token_id = uuid.uuid4().hex[:12]
        payload = {
            "sub": row["user_id"],
            "email": row["email"],
            "name": row["name"],
            "role": row["role"],
            "tier": row["tier"],
            "jti": token_id,
            "exp": exp,
        }
        access_token = _create_jwt(payload, secret)

        # Record session
        conn.execute(
            "INSERT INTO sessions (token_id, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)",
            (token_id, row["user_id"], time.time(), exp),
        )
        conn.commit()
        conn.close()

        return {
            "ok": True,
            "access_token": access_token,
            "token_type": "bearer",
            "expires_in": JWT_EXPIRY_HOURS * 3600,
            "user": {
                "user_id": row["user_id"],
                "email": row["email"],
                "name": row["name"],
                "role": row["role"],
                "tier": row["tier"],
            },
        }

    def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
        """Verify a JWT and return the user identity, or None."""
        secret = _get_jwt_secret()
        payload = _verify_jwt(token, secret)
        if not payload:
            return None

        # Check session not revoked
        conn = self._connect()
        row = conn.execute(
            "SELECT revoked FROM sessions WHERE token_id = ?",
            (payload.get("jti", ""),),
        ).fetchone()
        conn.close()

        if row and row["revoked"]:
            return None

        return payload

    def logout(self, token: str) -> bool:
        """Revoke a JWT session."""
        secret = _get_jwt_secret()
        payload = _verify_jwt(token, secret)
        if not payload:
            return False
        conn = self._connect()
        conn.execute(
            "UPDATE sessions SET revoked = 1 WHERE token_id = ?",
            (payload.get("jti", ""),),
        )
        conn.commit()
        conn.close()
        return True

    def get_user(self, user_id: str) -> Optional[Dict[str, Any]]:
        """Look up a user by ID."""
        conn = self._connect()
        row = conn.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)).fetchone()
        conn.close()
        if not row:
            return None
        return {
            "user_id": row["user_id"],
            "email": row["email"],
            "name": row["name"],
            "role": row["role"],
            "tier": row["tier"],
            "status": row["status"],
            "created_at": row["created_at"],
        }

    def list_users(self) -> list[Dict[str, Any]]:
        """List all users (admin only in production)."""
        conn = self._connect()
        rows = conn.execute("SELECT user_id, email, name, role, tier, status, created_at FROM users ORDER BY created_at DESC").fetchall()
        conn.close()
        return [dict(r) for r in rows]

    def update_tier(self, user_id: str, tier: str) -> bool:
        """Upgrade/downgrade a user's subscription tier."""
        if tier not in ("free", "core", "pro", "enterprise"):
            return False
        conn = self._connect()
        conn.execute("UPDATE users SET tier = ? WHERE user_id = ?", (tier, user_id))
        conn.commit()
        conn.close()
        return True
