"""Session repository.

Provides storage, retrieval, and deletion of session records in the
``sessions`` table of the application SQLite database.

Session tokens are stored as one-way SHA256 hashes to ensure that if the
database is exposed, the session tokens themselves cannot be directly used.
The hash is computed from the raw token before all database operations.
"""

from __future__ import annotations

import hashlib
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import aiosqlite

from app.models.auth import Session


def _hash_token(token: str) -> str:
    """Return the SHA256 hash of a session token.

    Args:
        token: The raw session token to hash.

    Returns:
        The hexadecimal SHA256 digest of the token.
    """
    return hashlib.sha256(token.encode()).hexdigest()


async def create_session(
    db: aiosqlite.Connection,
    token: str,
    created_at: str,
    expires_at: str,
) -> Session:
    """Insert a new session row and return the domain model.

    Args:
        db: Active aiosqlite connection.
        token: Opaque random session token (hex string).
        created_at: ISO 8601 UTC creation timestamp.
        expires_at: ISO 8601 UTC expiry timestamp.

    Returns:
        The newly created :class:`~app.models.auth.Session`.

    Note:
        The token is hashed before storage. The returned Session object
        contains the original raw token for use in signing and response.
    """
    token_hash = _hash_token(token)
    cursor = await db.execute(
        "INSERT INTO sessions (token_hash, created_at, expires_at) VALUES (?, ?, ?)",
        (token_hash, created_at, expires_at),
    )
    await db.commit()
    return Session(
        id=int(cursor.lastrowid) if cursor.lastrowid else 0,
        token=token,
        created_at=created_at,
        expires_at=expires_at,
    )


async def get_session(db: aiosqlite.Connection, token: str) -> Session | None:
    """Look up a session by its token.

    Args:
        db: Active aiosqlite connection.
        token: The session token to retrieve.

    Returns:
        The :class:`~app.models.auth.Session` if found, else ``None``.

    Note:
        The token is hashed before the database lookup.
    """
    token_hash = _hash_token(token)
    async with db.execute(
        "SELECT id, token_hash, created_at, expires_at FROM sessions WHERE token_hash = ?",
        (token_hash,),
    ) as cursor:
        row = await cursor.fetchone()

    if row is None:
        return None

    return Session(
        id=int(row[0]),
        token=token,
        created_at=str(row[2]),
        expires_at=str(row[3]),
    )


async def delete_session(db: aiosqlite.Connection, token: str) -> None:
    """Delete a session by token (logout / expiry clean-up).

    Args:
        db: Active aiosqlite connection.
        token: The session token to remove.

    Note:
        The token is hashed before the database lookup.
    """
    token_hash = _hash_token(token)
    await db.execute("DELETE FROM sessions WHERE token_hash = ?", (token_hash,))
    await db.commit()


async def delete_expired_sessions(db: aiosqlite.Connection, now_iso: str) -> int:
    """Remove all sessions whose ``expires_at`` timestamp is in the past.

    Args:
        db: Active aiosqlite connection.
        now_iso: Current UTC time as ISO 8601 string used as the cutoff.

    Returns:
        Number of rows deleted.
    """
    cursor = await db.execute(
        "DELETE FROM sessions WHERE expires_at <= ?",
        (now_iso,),
    )
    await db.commit()
    return int(cursor.rowcount)
