"""Unit tests for the repository testing contract helpers."""

from __future__ import annotations

from pathlib import Path
import textwrap

from testing.quality_contract import load_contract
from testing.quality_coverage import run_check as run_coverage_check
from testing.quality_docs import run_check as run_docs_check
from testing.quality_hygiene import run_check as run_hygiene_check


def test_bundled_contract_exposes_local_and_jenkins_profiles():
    contract = load_contract()
    assert "local" in contract["profiles"]
    assert "jenkins" in contract["profiles"]
    assert contract["pytest_suites"]["unit"]["paths"]


def test_docs_check_reports_missing_docstring_and_missing_path(tmp_path: Path):
    module_path = tmp_path / "managed.py"
    module_path.write_text("value = 1\n", encoding="utf-8")
    (tmp_path / "README.md").write_text("repo docs\n", encoding="utf-8")

    contract = {
        "required_docs": [{"path": "README.md", "description": "Docs"}],
        "managed_modules": ["managed.py"],
        "lint_paths": ["missing-dir"],
        "pytest_suites": {"unit": {"description": "Unit", "paths": ["missing-tests"]}},
        "manual_scripts": [{"path": "missing-script.py", "description": "Manual"}],
    }

    issues = run_docs_check(contract, tmp_path)

    assert "module docstring missing: managed.py" in issues
    assert "contract path missing: missing-dir" in issues
    assert "contract path missing: missing-tests" in issues
    assert "contract path missing: missing-script.py" in issues


def test_docs_check_reports_missing_required_doc_metadata(tmp_path: Path):
    (tmp_path / "README.md").write_text("", encoding="utf-8")

    contract = {
        "required_docs": [{"path": "README.md", "description": ""}, {"path": "missing.md", "description": "Missing"}],
        "managed_modules": [],
        "lint_paths": [],
        "pytest_suites": {"unit": {"description": "", "paths": []}},
        "manual_scripts": [{"path": "manual.py", "description": ""}],
    }

    issues = run_docs_check(contract, tmp_path)

    assert "required doc empty: README.md" in issues
    assert "required doc missing description: README.md" in issues
    assert "required doc missing: missing.md" in issues
    assert "pytest suite missing description: unit" in issues
    assert "manual script missing description: manual.py" in issues


def test_hygiene_check_enforces_line_limit_and_name_rules(tmp_path: Path):
    tests_dir = tmp_path / "tests"
    tests_dir.mkdir()
    bad_name = tests_dir / "bad-name.py"
    bad_name.write_text("x = 1\n", encoding="utf-8")
    long_file = tests_dir / "test_too_long.py"
    long_file.write_text("line\n" * 4, encoding="utf-8")

    contract = {
        "hygiene": {
            "max_lines": 3,
            "line_limit_globs": ["tests/*.py"],
            "naming_rules": [
                {
                    "glob": "tests/*.py",
                    "pattern": r"^test_[a-z0-9_]+\.py$",
                    "description": "pytest files use test_*.py names.",
                }
            ],
        }
    }

    issues = run_hygiene_check(contract, tmp_path)

    assert any("file exceeds 3 LOC" in issue for issue in issues)
    assert any("naming rule failed" in issue and "bad-name.py" in issue for issue in issues)


def test_coverage_check_enforces_per_file_floor(tmp_path: Path):
    build_dir = tmp_path / "build"
    build_dir.mkdir()
    coverage_xml = build_dir / "coverage.xml"
    coverage_xml.write_text(
        textwrap.dedent(
            """\
            <coverage>
              <packages>
                <package>
                  <classes>
                    <class filename="ok.py" line-rate="1.0" />
                    <class filename="low.py" line-rate="0.90" />
                  </classes>
                </package>
              </packages>
            </coverage>
            """
        ),
        encoding="utf-8",
    )

    contract = {
        "coverage": {
            "minimum_percent": 95.0,
            "tracked_files": ["ok.py", "low.py", "missing.py"],
        }
    }

    issues = run_coverage_check(contract, tmp_path, coverage_xml)

    assert "coverage below 95.0%: low.py (90.0%)" in issues
    assert "coverage missing for tracked file: missing.py" in issues


def test_coverage_check_handles_missing_xml_and_source_root_mapping(tmp_path: Path):
    missing_xml = tmp_path / "missing.xml"
    assert run_coverage_check({"coverage": {"tracked_files": []}}, tmp_path, missing_xml) == [
        "coverage xml missing: missing.xml"
    ]

    source_dir = tmp_path / "pkg"
    source_dir.mkdir()
    (source_dir / "mapped.py").write_text("value = 1\n", encoding="utf-8")
    coverage_xml = tmp_path / "coverage.xml"
    coverage_xml.write_text(
        textwrap.dedent(
            f"""\
            <coverage>
              <sources>
                <source>{source_dir}</source>
              </sources>
              <packages>
                <package>
                  <classes>
                    <class filename="mapped.py" line-rate="1.0" />
                    <class filename="{(tmp_path / 'absolute.py').as_posix()}" line-rate="1.0" />
                    <class filename="skip.py" />
                  </classes>
                </package>
              </packages>
            </coverage>
            """
        ),
        encoding="utf-8",
    )
    (tmp_path / "absolute.py").write_text("value = 2\n", encoding="utf-8")

    issues = run_coverage_check(
        {
            "coverage": {
                "minimum_percent": 95.0,
                "tracked_files": ["pkg/mapped.py", "absolute.py"],
            }
        },
        tmp_path,
        coverage_xml,
    )

    assert issues == []
