"""Blocklist sources repository.

CRUD operations for the ``blocklist_sources`` table in the application
SQLite database.  All methods accept a :class:`aiosqlite.Connection` — no
ORM, no HTTP exceptions.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    import aiosqlite


async def create_source(
    db: aiosqlite.Connection,
    name: str,
    url: str,
    *,
    enabled: bool = True,
) -> int:
    """Insert a new blocklist source and return its generated id.

    Args:
        db: Active aiosqlite connection.
        name: Human-readable display name.
        url: URL of the blocklist text file.
        enabled: Whether the source is active.  Defaults to ``True``.

    Returns:
        The ``ROWID`` / primary key of the new row.
    """
    cursor = await db.execute(
        """
        INSERT INTO blocklist_sources (name, url, enabled)
        VALUES (?, ?, ?)
        """,
        (name, url, int(enabled)),
    )
    await db.commit()
    return int(cursor.lastrowid)  # type: ignore[arg-type]


async def get_source(
    db: aiosqlite.Connection,
    source_id: int,
) -> dict[str, Any] | None:
    """Return a single blocklist source row as a plain dict, or ``None``.

    Args:
        db: Active aiosqlite connection.
        source_id: Primary key of the source to retrieve.

    Returns:
        A dict with keys matching the ``blocklist_sources`` columns, or
        ``None`` if no row with that id exists.
    """
    async with db.execute(
        "SELECT id, name, url, enabled, created_at, updated_at FROM blocklist_sources WHERE id = ?",
        (source_id,),
    ) as cursor:
        row = await cursor.fetchone()
        if row is None:
            return None
        return _row_to_dict(row)


async def list_sources(db: aiosqlite.Connection) -> list[dict[str, Any]]:
    """Return all blocklist sources ordered by id ascending.

    Args:
        db: Active aiosqlite connection.

    Returns:
        List of dicts, one per row in ``blocklist_sources``.
    """
    async with db.execute(
        "SELECT id, name, url, enabled, created_at, updated_at FROM blocklist_sources ORDER BY id"
    ) as cursor:
        rows = await cursor.fetchall()
        return [_row_to_dict(r) for r in rows]


async def list_enabled_sources(db: aiosqlite.Connection) -> list[dict[str, Any]]:
    """Return only enabled blocklist sources ordered by id.

    Args:
        db: Active aiosqlite connection.

    Returns:
        List of dicts for rows where ``enabled = 1``.
    """
    async with db.execute(
        "SELECT id, name, url, enabled, created_at, updated_at FROM blocklist_sources WHERE enabled = 1 ORDER BY id"
    ) as cursor:
        rows = await cursor.fetchall()
        return [_row_to_dict(r) for r in rows]


async def update_source(
    db: aiosqlite.Connection,
    source_id: int,
    *,
    name: str | None = None,
    url: str | None = None,
    enabled: bool | None = None,
) -> bool:
    """Update one or more fields on a blocklist source.

    Only the keyword arguments that are not ``None`` are included in the
    ``UPDATE`` statement.

    Args:
        db: Active aiosqlite connection.
        source_id: Primary key of the source to update.
        name: New display name, or ``None`` to leave unchanged.
        url: New URL, or ``None`` to leave unchanged.
        enabled: New enabled flag, or ``None`` to leave unchanged.

    Returns:
        ``True`` if a row was updated, ``False`` if the id does not exist.
    """
    fields: list[str] = []
    params: list[Any] = []

    if name is not None:
        fields.append("name = ?")
        params.append(name)
    if url is not None:
        fields.append("url = ?")
        params.append(url)
    if enabled is not None:
        fields.append("enabled = ?")
        params.append(int(enabled))

    if not fields:
        # Nothing to update — treat as success only if the row exists.
        return await get_source(db, source_id) is not None

    fields.append("updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')")
    params.append(source_id)

    cursor = await db.execute(
        f"UPDATE blocklist_sources SET {', '.join(fields)} WHERE id = ?",  # noqa: S608
        params,
    )
    await db.commit()
    return cursor.rowcount > 0


async def delete_source(db: aiosqlite.Connection, source_id: int) -> bool:
    """Delete a blocklist source by id.

    Args:
        db: Active aiosqlite connection.
        source_id: Primary key of the source to remove.

    Returns:
        ``True`` if a row was deleted, ``False`` if the id did not exist.
    """
    cursor = await db.execute(
        "DELETE FROM blocklist_sources WHERE id = ?",
        (source_id,),
    )
    await db.commit()
    return cursor.rowcount > 0


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------


def _row_to_dict(row: Any) -> dict[str, Any]:
    """Convert an aiosqlite row to a plain Python dict.

    Args:
        row: An :class:`aiosqlite.Row` or sequence returned by a cursor.

    Returns:
        ``dict`` mapping column names to values with ``enabled`` cast to
        ``bool``.
    """
    d: dict[str, Any] = dict(row)
    d["enabled"] = bool(d["enabled"])
    return d
