"""Lint test: no hardcoded page_size defaults exist in routers.

Ensures all paginated endpoints derive their default from
:data:`~app.utils.constants.DEFAULT_PAGE_SIZE` rather than
hardcoding integer literals.
"""

from __future__ import annotations

import ast
from pathlib import Path

import pytest

ROUTERS_DIR = Path(__file__).parent.parent.parent / "app" / "routers"


class _PageSizeDefaultChecker(ast.NodeVisitor):
    """Visit Python source files and report hardcoded page_size defaults."""

    def __init__(self, source: str) -> None:
        self.source = source
        self.lines = source.splitlines()
        self.violations: list[str] = []

    def _line_for(self, node: ast.AST) -> int:
        return node.lineno  # type: ignore[attr-defined]

    def _extract_assign_value(self, target: ast.expr, value: ast.expr) -> int | None:
        """Return int value if *value* is an ast.Constant/ast.Num with an int."""
        if isinstance(value, ast.Constant) and isinstance(value.value, int):
            return value.value
        if isinstance(value, ast.Num) and isinstance(value.n, int):
            return value.n
        return None

    def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
        for default in node.args.defaults:
            self._check_default(default, node.name)
        for default in node.args.kw_defaults:
            if default is not None:
                self._check_default(default, node.name)
        self.generic_visit(node)

    def _check_default(self, node: ast.expr, fn_name: str) -> None:
        # Look for patterns:
        #   page_size: SomeType = 25
        #   page_size: int = 25
        if isinstance(node, ast.Constant) and isinstance(node.value, int):
            lineno = node.lineno  # type: ignore[attr-defined]
            col = node.col_offset  # type: ignore[attr-defined]
            self.violations.append(
                f"line {lineno}:{col}  {fn_name}  page_size default is hardcoded int ({node.value}); "
                "use DEFAULT_PAGE_SIZE from app.utils.constants"
            )


def _check_file(path: Path) -> list[str]:
    """Return list of violation messages for *path* (empty if clean)."""
    try:
        source = path.read_text()
    except Exception as exc:
        return [f"could not read {path}: {exc}"]

    tree = ast.parse(source, filename=str(path))
    checker = _PageSizeDefaultChecker(source)
    checker.visit(tree)

    # Filter to only violations that involve page_size.
    # The checker already visits FunctionDef defaults broadly; we narrow to
    # those that look like "page_size = <int>" by inspecting the line content.
    filtered: list[str] = []
    for msg in checker.violations:
        # Extract line number from "line N:"
        lineno = int(msg.split(":")[1])
        line_text = source.splitlines()[lineno - 1]
        if "page_size" in line_text:
            filtered.append(msg)
    return filtered


@pytest.mark.parametrize("router", sorted(ROUTERS_DIR.glob("*.py")), ids=lambda p: p.name)
def test_no_hardcoded_page_size_defaults(router: Path) -> None:
    """Router files must not contain hardcoded integer defaults for page_size.

    All paginated endpoints should use ``default=DEFAULT_PAGE_SIZE`` from
    :data:`~app.utils.constants.DEFAULT_PAGE_SIZE`.
    """
    violations = _check_file(router)
    assert violations == [], f"Hardcoded page_size defaults found in {router.name}:\n" + "\n".join(violations)
