[project]
name = "testsandmisc"
version = "0.1.0"
description = "Collection of miscellaneous tests and scripts"
requires-python = ">=3.10"

# ============================================================================
# RUFF - Extremely fast Python linter and formatter (written in Rust)
# ============================================================================
[tool.ruff]
target-version = "py310"
# Include all Python files
include = ["*.py", "**/*.py"]
# Exclude vendored/build directories
exclude = [
    ".git",
    ".venv",
    "__pycache__",
    "build",
    "dist",
    ".eggs",
    "Bash/ffmpeg-build",  # Vendored FFmpeg tools
]

[tool.ruff.lint]
# AGGRESSIVE: Select ALL rules from all categories
select = ["ALL"]
# Ignores for rules that are too strict for this mixed script repository
ignore = [
    # D203 vs D211 conflict - we use D211 (no blank line before class docstring)
    "D203",    # 1 blank line required before class docstring (conflicts with D211)
    # D212 vs D213 conflict - we use D212 (summary on first line after """)
    "D213",    # Multi-line docstring summary should start at second line (conflicts with D212)
    # Formatter conflicts - recommended to disable when using ruff format
    # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
    "COM812",  # Trailing comma missing - formatter handles this automatically
    "ISC001",  # Implicit string concatenation - formatter may create these when wrapping
    # Security audit - prone to false positives with validated input
    # https://github.com/astral-sh/ruff/issues/4045
    "S603",    # subprocess call without shell - prone to false positives as it is
               # difficult to determine whether the passed arguments have been validated
]

# Allow ALL rules to be auto-fixed
fixable = ["ALL"]
unfixable = []

# Per-file ignores — only rules that FUNDAMENTALLY conflict with test code remain.
# Every other rule was fixed in source. See justifications below.
[tool.ruff.lint.per-file-ignores]
"**/tests/**/*.py" = [
    "ARG",     # @patch decorators inject mock params that aren't always referenced;
               #   the patch side-effect is needed, not the mock object itself.
    "D",       # Test names like test_sub_cards_no_answer_text are self-documenting;
               #   docstrings would be redundant noise on every test method.
    "PLC0415", # Test isolation requires importing AFTER mocking sys.modules;
               #   top-level imports would bypass the mocks entirely.
    "PLR2004", # assert count == 5 is clearer than assert count == EXPECTED_COUNT;
               #   named constants for test expectations add indirection without value.
    "S101",    # assert IS what tests do — every Python test suite suppresses this.
    "SLF001",  # Unit tests must exercise private internals (_method, _attr) to reach
               #   100% branch coverage; only integration tests can avoid this.
]
"**/test_*.py" = [
    "ARG",
    "D",
    "PLC0415",
    "PLR2004",
    "S101",
    "SLF001",
]


[tool.ruff.lint.pydocstyle]
convention = "google"  # Use Google docstring convention

[tool.ruff.lint.isort]
force-single-line = false
force-sort-within-sections = true
known-first-party = ["python_pkg"]

[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"

[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = true

# ============================================================================
# MYPY - Static type checker (most aggressive settings)
# ============================================================================
[tool.mypy]
python_version = "3.10"
# Strict mode enables most checks
strict = true
# Additional aggressive settings
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
# Extra strict settings
disallow_any_unimported = true
disallow_any_explicit = false  # Too aggressive for practical use
disallow_any_generics = true
disallow_subclassing_any = true
strict_equality = true
extra_checks = true
# Allow missing imports for third-party packages
ignore_missing_imports = true
# Show error codes
show_error_codes = true
# Enable colored output
color_output = true
# Exclude vendored directories
exclude = [
    "Bash/ffmpeg-build/",
    ".venv/",
    "linux_configuration/scripts/single_use/misc/testsAndMisc-bash/tools/",  # Avoid duplicate module named 'tools'
]

# Standalone tool scripts outside python_pkg/ are not held to strict mypy rules
[[tool.mypy.overrides]]
module = "linux_configuration.*"
ignore_errors = true

# ============================================================================
# PYLINT - Comprehensive Python linter
# ============================================================================
[tool.pylint.main]
# Analyse import fallback blocks
analyse-fallback-blocks = true
# Pickle collected data for later comparisons
persistent = true
# Jobs to use for parallel execution (0 = auto)
jobs = 0
# Minimum Python version
py-version = "3.10"
# Ignore vendored directories
ignore = ["Bash", ".venv", "__pycache__"]
# Ignore patterns
ignore-patterns = [".*\\.pyi$"]
# Allow C extension modules to be introspected
extension-pkg-allow-list = ["cv2", "pygame", "lxml"]

[tool.pylint.messages_control]
# Enable all checks by disabling disable
enable = "all"
# No disabled checks - maximum strictness
disable = []

[tool.pylint.design]
# Mixins and single-entry-point classes may have zero public methods
min-public-methods = 0
# Test modules can be large
max-module-lines = 1000
# UI/mixin classes accumulate attributes across multiple mixins
max-attributes = 10

[tool.pylint.spelling]
# No spelling dictionary to avoid false positives
spelling-dict = ""

[tool.pylint.typecheck]
# cv2 (OpenCV) dynamically loads members from C extension at runtime.
# unittest.mock.MagicMock generates assertion/introspection methods at runtime.
generated-members = [
    "cv2.*",
    ".*\\.assert_called_once_with",
    ".*\\.assert_called_once",
    ".*\\.assert_called",
    ".*\\.assert_not_called",
    ".*\\.assert_any_call",
    ".*\\.call_args",
    ".*\\.call_args_list",
    ".*\\.call_count",
]

# ============================================================================
# BANDIT - Security linter
# ============================================================================
[tool.bandit]
# Exclude test directories and vendored code
exclude_dirs = ["tests", ".venv", "Bash/ffmpeg-build"]

# ============================================================================
# BLACK & ISORT - Removed (ruff handles formatting and import sorting)
# ============================================================================

# ============================================================================
# PYTEST - Testing framework configuration
# ============================================================================
[tool.pytest.ini_options]
# linux_configuration/tests covers the standalone usage_report scripts. It adds
# no python_pkg coverage (those tests don't import python_pkg), so running it
# alongside leaves the 100%-on-python_pkg gate untouched.
testpaths = ["python_pkg", "linux_configuration/tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
    "-v",
    "--strict-markers",
    "--strict-config",
    "-ra",
    "--cov=python_pkg",
    "--cov-branch",
    "--cov-report=term-missing",
    "--cov-report=lcov",
]
filterwarnings = [
    "error",
    "ignore::DeprecationWarning",
    "default::pytest.PytestUnraisableExceptionWarning",
]

# ============================================================================
# COVERAGE - Code coverage configuration
# ============================================================================
[tool.coverage.run]
source = ["python_pkg"]
branch = true
omit = [
    "*/__pycache__/*",
    "*/tests/*",
    "*/.venv/*",
]

[tool.coverage.report]
# Fail under this percentage
fail_under = 100
show_missing = true
skip_covered = false
exclude_lines = [
    # Standard exclusions
    "pragma: no cover",
    # Unreachable defensive code
    "raise NotImplementedError",
    "raise AssertionError",
    # Type checking imports
    "if TYPE_CHECKING:",
    # Main script entry point
    'if __name__ == "__main__":',
]
# Partial branch exclusions for unreachable branches
partial_branches = [
    "pragma: no branch",
]

# ============================================================================
# VULTURE - Dead code detection
# ============================================================================
# Note: Vulture uses command-line args, but we can document settings here
# vulture --min-confidence 80 --exclude ".venv,Bash" .

# ============================================================================
# FLAKE8 - Python linter (via Flake8-pyproject for pyproject.toml support)
# ============================================================================
[tool.flake8]
# Maximum line length (matches ruff/black)
max-line-length = 88
# Maximum McCabe complexity (matches ruff C901 threshold)
max-complexity = 10
# Maximum cognitive complexity (flake8-cognitive-complexity)
max-cognitive-complexity = 12
# Maximum function length (flake8-functions)
max-function-length = 20
# Maximum returns/arguments per function
max-returns-amount = 6
max-arguments = 5
# Docstring convention (matches ruff)
docstring-convention = "google"
# Select all error codes
select = ["E", "F", "W", "C", "B", "B950"]
# Extend with plugin codes
extend-select = ["B", "B9", "C4", "SIM", "PT", "TC", "ANN"]
# Ignore rules that conflict with ruff-format or are duplicated
extend-ignore = [
    "E501",   # Line too long - B950 from bugbear is smarter (allows 10% overflow)
    "W503",   # Line break before binary operator - contradicts PEP 8 update
    "ANN101", # Missing type annotation for self
    "ANN102", # Missing type annotation for cls
]
# Exclude directories
exclude = [
    ".git",
    ".venv",
    "__pycache__",
    "build",
    "dist",
    ".eggs",
    "Bash/ffmpeg-build",
]
# Per-file ignores
per-file-ignores = [
    "**/tests/**/*.py:S101,ANN",
    "**/test_*.py:S101,ANN",
]

# ============================================================================
# PYDOCSTYLE - Docstring style checker (ruff handles this, but for standalone)
# ============================================================================
# Configured in ruff.lint.pydocstyle above
