"""Step definitions for coverage threshold configuration feature."""

import re
from pathlib import Path

from behave import given, then
from behave.runner import Context


@given("the pyproject toml file is loaded for coverage check")
def step_load_pyproject_for_coverage(context: Context) -> None:
    """Load and parse pyproject.toml for coverage configuration checks."""
    toml_path = Path(__file__).resolve().parent.parent.parent / "pyproject.toml"
    if not toml_path.exists():
        raise FileNotFoundError(f"pyproject.toml not found at {toml_path}")
    context.pyproject_text = toml_path.read_text(encoding="utf-8")


@given("the noxfile py is loaded for coverage check")
def step_load_noxfile_for_coverage(context: Context) -> None:
    """Load noxfile.py for coverage session checks."""
    nox_path = Path(__file__).resolve().parent.parent.parent / "noxfile.py"
    if not nox_path.exists():
        raise FileNotFoundError(f"noxfile.py not found at {nox_path}")
    context.noxfile_text = nox_path.read_text(encoding="utf-8")


@given("the ci workflow file is loaded for coverage check")
def step_load_ci_workflow_for_coverage(context: Context) -> None:
    """Load CI workflow file for coverage enforcement checks."""
    ci_path = (
        Path(__file__).resolve().parent.parent.parent
        / ".forgejo"
        / "workflows"
        / "ci.yml"
    )
    if not ci_path.exists():
        raise FileNotFoundError(f"CI workflow not found at {ci_path}")
    context.ci_workflow_text = ci_path.read_text(encoding="utf-8")


@given("the nightly quality workflow file is loaded for coverage check")
def step_load_nightly_workflow_for_coverage(context: Context) -> None:
    """Load nightly quality workflow file for coverage threshold checks."""
    nightly_path = (
        Path(__file__).resolve().parent.parent.parent
        / ".forgejo"
        / "workflows"
        / "nightly-quality.yml"
    )
    if not nightly_path.exists():
        raise FileNotFoundError(f"Nightly workflow not found at {nightly_path}")
    context.nightly_workflow_text = nightly_path.read_text(encoding="utf-8")


@then("the coverage run section should exist")
def step_coverage_run_section_exists(context: Context) -> None:
    """Assert [tool.coverage.run] section is present."""
    if "[tool.coverage.run]" not in context.pyproject_text:
        raise AssertionError("[tool.coverage.run] section missing from pyproject.toml")


@then('the coverage source should include "{source}"')
def step_coverage_source_includes(context: Context, source: str) -> None:
    """Assert coverage source includes the given directory."""
    # Parse the source list from pyproject.toml
    match = re.search(r"source\s*=\s*\[([^\]]+)\]", context.pyproject_text)
    if not match:
        raise AssertionError("No source list found in coverage config")
    sources = match.group(1)
    if f'"{source}"' not in sources:
        raise AssertionError(
            f'Expected "{source}" in coverage source list, got: {sources}'
        )


@then("the coverage branch tracking should be enabled")
def step_coverage_branch_enabled(context: Context) -> None:
    """Assert branch coverage is enabled."""
    if "branch = true" not in context.pyproject_text:
        raise AssertionError("branch = true not found in coverage config")


@then("the noxfile should contain a fail-under threshold of at least {threshold:d}")
def step_noxfile_fail_under(context: Context, threshold: int) -> None:
    """Assert noxfile has fail-under >= given threshold.

    Supports both literal ``--fail-under=97`` and f-string
    ``f"--fail-under={COVERAGE_THRESHOLD}"`` patterns. When the
    f-string form is found, the COVERAGE_THRESHOLD constant value
    is resolved from the source.
    """
    import ast

    # First try literal --fail-under=N
    matches = re.findall(r"--fail-under=(\d+)", context.noxfile_text)
    if matches:
        max_threshold = max(int(m) for m in matches)
        if max_threshold < threshold:
            raise AssertionError(
                f"fail-under={max_threshold} is below required {threshold}"
            )
        return

    # Fall back: check for f-string referencing COVERAGE_THRESHOLD constant
    if (
        "fail-under" in context.noxfile_text
        and "COVERAGE_THRESHOLD" in context.noxfile_text
    ):
        tree = ast.parse(context.noxfile_text)
        for node in ast.walk(tree):
            if isinstance(node, ast.Assign):
                for target in node.targets:
                    if (
                        isinstance(target, ast.Name)
                        and target.id == "COVERAGE_THRESHOLD"
                        and isinstance(node.value, ast.Constant)
                    ):
                        raw = node.value.value
                        value = int(str(raw))
                        if value < threshold:
                            raise AssertionError(
                                f"COVERAGE_THRESHOLD={value} is below required {threshold}"
                            )
                        return
    raise AssertionError("No --fail-under found in noxfile.py")


@then("the noxfile should define a coverage_report session")
def step_noxfile_coverage_session_exists(context: Context) -> None:
    """Assert that a coverage_report session is defined in noxfile."""
    if "def coverage_report(" not in context.noxfile_text:
        raise AssertionError("coverage_report session not found in noxfile.py")


@then('the coverage html directory should be "{directory}"')
def step_coverage_html_directory(context: Context, directory: str) -> None:
    """Assert HTML coverage output directory."""
    if f'directory = "{directory}"' not in context.pyproject_text:
        raise AssertionError(
            f'Expected directory = "{directory}" in [tool.coverage.html]'
        )


@then('the coverage xml output should be "{output}"')
def step_coverage_xml_output(context: Context, output: str) -> None:
    """Assert XML coverage output path."""
    if f'output = "{output}"' not in context.pyproject_text:
        raise AssertionError(f'Expected output = "{output}" in [tool.coverage.xml]')


@then('the coverage data file should be "{data_file}"')
def step_coverage_data_file(context: Context, data_file: str) -> None:
    """Assert coverage data file path."""
    if f'data_file = "{data_file}"' not in context.pyproject_text:
        raise AssertionError(
            f'Expected data_file = "{data_file}" in [tool.coverage.run]'
        )


@then('the coverage omit patterns should include "{pattern}"')
def step_coverage_omit_includes(context: Context, pattern: str) -> None:
    """Assert coverage omit list includes the given pattern."""
    if f'"{pattern}"' not in context.pyproject_text:
        raise AssertionError(f'Expected "{pattern}" in coverage omit patterns')


@then("the ci workflow should reference nox coverage_report session")
def step_ci_coverage_nox_session(context: Context) -> None:
    """Assert CI workflow runs nox -s coverage_report."""
    if "nox -s coverage_report" not in context.ci_workflow_text:
        raise AssertionError("CI workflow does not reference 'nox -s coverage_report'")


@then("the nightly workflow should use a fail-under of at least {threshold:d}")
def step_nightly_fail_under(context: Context, threshold: int) -> None:
    """Assert nightly workflow uses fail-under >= threshold."""
    # Check for --fail-under=N pattern (slipcover/coverage CLI)
    matches = re.findall(r"--fail-under=(\d+)", context.nightly_workflow_text)
    # Also check for --coverage-min N pattern (quality gates script)
    matches += re.findall(r"--coverage-min\s+(\d+)", context.nightly_workflow_text)
    if not matches:
        raise AssertionError(
            "No --fail-under or --coverage-min found in nightly workflow"
        )
    max_threshold = max(int(m) for m in matches)
    if max_threshold < threshold:
        raise AssertionError(
            f"Nightly fail-under={max_threshold} is below required {threshold}"
        )
