"""JWT authentication for Muleline. Registration, login, token management."""
from __future__ import annotations

import hashlib
import hmac
import os
import re
import time

import jwt

from muleline.config import JWT_SECRET
from muleline.db import get_db

TOKEN_EXPIRY = 7 * 24 * 3600  # 7 days
REFRESH_EXPIRY = 30 * 24 * 3600  # 30 days

_USERNAME_RE = re.compile(r"^[a-zA-Z0-9._@-]{3,64}$")


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


def _verify_password(password: str, salt_hex: str, hash_hex: str) -> bool:
    salt = bytes.fromhex(salt_hex)
    dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 100_000)
    return hmac.compare_digest(dk.hex(), hash_hex)


def create_token(user_id: str, username: str, token_type: str = "access") -> str:
    expiry = TOKEN_EXPIRY if token_type == "access" else REFRESH_EXPIRY
    payload = {
        "sub": user_id,
        "username": username,
        "type": token_type,
        "iat": int(time.time()),
        "exp": int(time.time()) + expiry,
    }
    return jwt.encode(payload, JWT_SECRET, algorithm="HS256")


def decode_token(token: str) -> dict | None:
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
        return payload
    except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
        return None


def register_user(username: str, password: str) -> dict:
    if not _USERNAME_RE.match(username):
        return {"error": "Invalid username (3-64 chars, alphanumeric/._@-)"}
    if len(password) < 6:
        return {"error": "Password must be at least 6 characters"}

    conn = get_db()
    try:
        existing = conn.execute(
            "SELECT id FROM users WHERE username = ?", (username,)
        ).fetchone()
        if existing:
            return {"error": "Username already taken"}

        salt, pw_hash = _hash_password(password)
        now = time.time()
        conn.execute(
            "INSERT INTO users (username, password_hash, password_salt, created_at) VALUES (?,?,?,?)",
            (username, pw_hash, salt, now),
        )
        user_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
        conn.commit()

        access = create_token(str(user_id), username, "access")
        refresh = create_token(str(user_id), username, "refresh")
        return {"ok": True, "user_id": user_id, "username": username, "access_token": access, "refresh_token": refresh}
    finally:
        conn.close()


def login_user(username: str, password: str) -> dict:
    conn = get_db()
    try:
        row = conn.execute(
            "SELECT id, username, password_hash, password_salt FROM users WHERE username = ?",
            (username,),
        ).fetchone()
        if not row:
            return {"error": "Invalid credentials"}

        if not _verify_password(password, row["password_salt"], row["password_hash"]):
            return {"error": "Invalid credentials"}

        access = create_token(str(row["id"]), row["username"], "access")
        refresh = create_token(str(row["id"]), row["username"], "refresh")
        return {"ok": True, "user_id": row["id"], "username": row["username"], "access_token": access, "refresh_token": refresh}
    finally:
        conn.close()


def refresh_token(token: str) -> dict:
    payload = decode_token(token)
    if not payload:
        return {"error": "Invalid or expired token"}
    if payload.get("type") != "refresh":
        return {"error": "Not a refresh token"}

    access = create_token(payload["sub"], payload["username"], "access")
    return {"ok": True, "access_token": access}
