# retoor <retoor@molodetz.nl>

from __future__ import annotations

from devplacepy.database import (
    get_table,
    text_search_clause,
)

from .policy import soft_delete_aware

SEARCH_FIELDS = (
    "title",
    "description",
    "content",
    "name",
    "username",
    "email",
    "body",
    "summary",
    "slug",
)
MAX_SYNC_LIMIT = 500


class DbApiError(Exception):
    pass


def column_names(table_name: str) -> list[str]:
    return list(get_table(table_name).columns)


def schema(table_name: str) -> dict:
    table = get_table(table_name)
    columns = []
    for column in table.table.columns:
        columns.append({"name": column.name, "type": str(column.type)})
    return {
        "table": table_name,
        "columns": columns,
        "soft_delete": soft_delete_aware(table_name),
        "row_count": table.count(),
    }


def _cursor_field(table) -> str:
    return "created_at" if table.has_column("created_at") else "id"


def list_rows(
    table_name: str,
    *,
    filters: dict | None = None,
    comparisons: dict | None = None,
    search: str = "",
    before=None,
    limit: int = 25,
    include_deleted: bool = False,
) -> tuple[list[dict], object]:
    table = get_table(table_name)
    cols = set(column_names(table_name))
    limit = max(1, min(int(limit or 25), MAX_SYNC_LIMIT))
    cursor_field = _cursor_field(table)
    clauses = []
    column_objs = table.table.columns
    if soft_delete_aware(table_name) and "deleted_at" in cols and not include_deleted:
        clauses.append(column_objs.deleted_at.is_(None))
    fields = tuple(field for field in SEARCH_FIELDS if field in cols)
    search_clause = text_search_clause(table, search, fields=fields) if fields else None
    if search_clause is not None:
        clauses.append(search_clause)
    if before is not None:
        clauses.append(column_objs[cursor_field] < before)
    kwargs = {}
    for key, value in (filters or {}).items():
        if key in cols:
            kwargs[key] = value
    for key, expression in (comparisons or {}).items():
        if key in cols:
            kwargs[key] = expression
    rows = list(
        table.find(*clauses, order_by=["-" + cursor_field], _limit=limit + 1, **kwargs)
    )
    has_more = len(rows) > limit
    rows = rows[:limit]
    next_cursor = rows[-1][cursor_field] if has_more and rows else None
    return [dict(row) for row in rows], next_cursor


def count_rows(table_name: str, filters: dict | None = None) -> int:
    table = get_table(table_name)
    cols = set(column_names(table_name))
    kwargs = {key: value for key, value in (filters or {}).items() if key in cols}
    return table.count(**kwargs)


def get_row(table_name: str, key: str, value) -> dict | None:
    _assert_key(table_name, key)
    row = get_table(table_name).find_one(**{key: value})
    return dict(row) if row else None


def _assert_key(table_name: str, key: str) -> None:
    if key not in set(column_names(table_name)):
        raise DbApiError(f"Unknown key column {key!r} for {table_name}.")
