"""Documentation-oriented validation for the testing contract."""

from __future__ import annotations

import ast
from pathlib import Path
from typing import Any


def _module_has_docstring(path: Path) -> bool:
    source = path.read_text(encoding="utf-8")
    return ast.get_docstring(ast.parse(source)) is not None


def _iter_contract_paths(contract: dict[str, Any]) -> list[str]:
    paths: list[str] = []
    for item in contract.get("required_docs", []):
        paths.append(item["path"])
    paths.extend(contract.get("managed_modules", []))
    paths.extend(contract.get("lint_paths", []))
    for suite in contract.get("pytest_suites", {}).values():
        paths.extend(suite.get("paths", []))
    for item in contract.get("manual_scripts", []):
        paths.append(item["path"])
    return paths


def run_check(contract: dict[str, Any], root: Path) -> list[str]:
    """Return human-readable issues for contract/documentation violations."""
    issues: list[str] = []

    for item in contract.get("required_docs", []):
        path = root / item["path"]
        if not path.exists():
            issues.append(f"required doc missing: {item['path']}")
            continue
        if path.is_file() and not path.read_text(encoding="utf-8").strip():
            issues.append(f"required doc empty: {item['path']}")
        if not item.get("description", "").strip():
            issues.append(f"required doc missing description: {item['path']}")

    for relative_path in sorted(set(_iter_contract_paths(contract))):
        if not (root / relative_path).exists():
            issues.append(f"contract path missing: {relative_path}")

    for suite_name, suite in contract.get("pytest_suites", {}).items():
        if not suite.get("description", "").strip():
            issues.append(f"pytest suite missing description: {suite_name}")

    for item in contract.get("manual_scripts", []):
        if not item.get("description", "").strip():
            issues.append(f"manual script missing description: {item['path']}")

    for relative_path in contract.get("managed_modules", []):
        path = root / relative_path
        if path.exists() and path.suffix == ".py" and not _module_has_docstring(path):
            issues.append(f"module docstring missing: {relative_path}")

    return issues
