import bcrypt
import secrets
from datetime import datetime, timedelta, timezone
from nicegui import app
from typing import Optional
import db


async def verify_login(email: str, password: str) -> Optional[dict]:
    """Return user dict if credentials are valid, else None."""
    row = await db.fetchrow(
        """
        SELECT id, email, name, is_superadmin, password_hash
        FROM user_account
        WHERE email = $1
        """,
        email.lower().strip(),
    )
    if not row or not row["password_hash"]:
        return None
    if bcrypt.checkpw(password.encode(), row["password_hash"].encode()):
        return {**dict(row), "id": str(row["id"])}
    return None


async def hash_password(password: str) -> str:
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()


async def set_password(user_id: str, password: str) -> None:
    hashed = await hash_password(password)
    await db.execute(
        "UPDATE user_account SET password_hash = $1 WHERE id = $2",
        hashed,
        user_id,
    )


def current_user() -> Optional[dict]:
    return app.storage.user.get("user")


async def sync_session() -> None:
    """Re-fetch the current user from the DB and update the session cache."""
    user = current_user()
    if not user:
        return
    fresh = await db.fetchrow(
        "SELECT id, email, name, is_superadmin FROM user_account WHERE id = $1",
        user["id"],
    )
    if fresh:
        app.storage.user["user"] = {**dict(fresh), "id": str(fresh["id"])}
    else:
        # User was deleted — clear session
        app.storage.user.clear()


def is_authenticated() -> bool:
    return current_user() is not None


def is_superadmin() -> bool:
    user = current_user()
    return bool(user and user.get("is_superadmin"))


async def create_reset_token(email: str) -> Optional[str]:
    """Generate a password reset token for the given email. Returns token or None if not found."""
    row = await db.fetchrow(
        "SELECT id FROM user_account WHERE email = $1",
        email.lower().strip(),
    )
    if not row:
        return None
    token = secrets.token_urlsafe(32)
    expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
    await db.execute(
        "INSERT INTO password_reset_token (user_id, token, expires_at) VALUES ($1, $2, $3)",
        row["id"],
        token,
        expires_at,
    )
    return token


async def verify_reset_token(token: str) -> Optional[dict]:
    """Return user + token info if the token is valid, unused, and not expired."""
    row = await db.fetchrow(
        """
        SELECT ua.id, ua.email, ua.name, prt.id AS token_id
        FROM   password_reset_token prt
        JOIN   user_account ua ON ua.id = prt.user_id
        WHERE  prt.token      = $1
          AND  prt.used_at    IS NULL
          AND  prt.expires_at > now()
        """,
        token,
    )
    return dict(row) if row else None


async def consume_reset_token(token_id: str, user_id: str, new_password: str) -> None:
    """Mark the token as used and update the user's password atomically."""
    hashed = await hash_password(new_password)
    await db.execute(
        "UPDATE password_reset_token SET used_at = now() WHERE id = $1",
        token_id,
    )
    await db.execute(
        "UPDATE user_account SET password_hash = $1 WHERE id = $2",
        hashed,
        user_id,
    )


async def get_user_organizations(user_id: str) -> list:
    """Return organizations the user is a member of (with role)."""
    return await db.fetch(
        """
        SELECT i.id, i.name, ui.role
        FROM user_organization ui
        JOIN organization i ON i.id = ui.organization_id
        WHERE ui.user_id = $1
        ORDER BY i.name
        """,
        user_id,
    )
