import hashlib
import secrets
from datetime import datetime, timezone

from sqlalchemy import select

from scribe.models import async_session
from scribe.models.api_key import ApiKey


def generate_key() -> str:
    """Generate a new full API key. Never stored — caller must hash it."""
    return "fmcp_" + secrets.token_urlsafe(32)


def hash_token(raw: str) -> str:
    """The ONE fingerprint for every bearer secret stored by hash — API keys,
    password-reset tokens, invitation tokens. Stored rows hold this, never
    the raw value; a lookup hashes the presented token and compares."""
    return hashlib.sha256(raw.encode()).hexdigest()


def _key_prefix(key: str) -> str:
    """Return first 12 chars of the key for display (e.g. 'fmcp_abcdefg')."""
    return key[:12]


async def create_api_key(
    user_id: int, name: str, scope: str
) -> tuple[str, dict]:
    """Create a new API key. Returns (full_key, key_dict). full_key is never stored."""
    if scope not in ("read", "write"):
        raise ValueError("scope must be 'read' or 'write'")
    full_key = generate_key()
    key = ApiKey(
        user_id=user_id,
        name=name,
        key_hash=hash_token(full_key),
        key_prefix=_key_prefix(full_key),
        scope=scope,
    )
    async with async_session() as session:
        session.add(key)
        await session.commit()
        await session.refresh(key)
        return full_key, key.to_dict()


async def list_api_keys(user_id: int) -> list[dict]:
    """List all non-revoked API keys for the user."""
    async with async_session() as session:
        result = await session.execute(
            select(ApiKey)
            .where(ApiKey.user_id == user_id, ApiKey.revoked_at.is_(None))
            .order_by(ApiKey.created_at.desc())
        )
        return [k.to_dict() for k in result.scalars().all()]


async def revoke_api_key(user_id: int, key_id: int) -> bool:
    """Soft-delete a key by setting revoked_at. Returns True if found."""
    async with async_session() as session:
        result = await session.execute(
            select(ApiKey).where(ApiKey.id == key_id, ApiKey.user_id == user_id)
        )
        key = result.scalars().first()
        if key is None:
            return False
        key.revoked_at = datetime.now(timezone.utc)
        await session.commit()
        return True


async def lookup_key(raw_key: str) -> ApiKey | None:
    """Look up a non-revoked ApiKey by raw token value. Updates last_used_at."""
    key_hash = hash_token(raw_key)
    async with async_session() as session:
        result = await session.execute(
            select(ApiKey).where(
                ApiKey.key_hash == key_hash,
                ApiKey.revoked_at.is_(None),
            )
        )
        key = result.scalars().first()
        if key is None:
            return None
        key.last_used_at = datetime.now(timezone.utc)
        await session.commit()
        await session.refresh(key)
        return key
