"""Validation tests for infrastructure documentation.

Ensures code examples in infrastructure docs are syntactically valid,
file sizes comply with standards, and cost estimates have proper dates.
"""

import ast
import re
from pathlib import Path

import pytest


def get_infrastructure_docs() -> list[Path]:
    """Get all infrastructure documentation markdown files."""
    docs_path = Path(".claude/knowledge/infrastructure")
    # Resolve to absolute paths to avoid relative_to() issues
    return [p.resolve() for p in docs_path.glob("*.md")]


def get_playbook_docs() -> list[Path]:
    """Get all playbook documentation files."""
    playbooks_path = Path("playbooks")
    # Resolve to absolute paths to avoid relative_to() issues
    return [p.resolve() for p in playbooks_path.glob("*.md") if "infrastructure" in p.name.lower()]


def get_relative_path(md_file: Path) -> Path:
    """Get relative path from resolved absolute path.

    Args:
        md_file: Absolute path to markdown file

    Returns:
        Relative path from current working directory
    """
    return md_file.relative_to(Path.cwd().resolve())


def extract_python_code_blocks(md_file: Path) -> list[tuple[str, int]]:
    """Extract Python code blocks from markdown with line numbers.

    Args:
        md_file: Path to markdown file

    Returns:
        List of tuples (code_block, starting_line_number)
    """
    content = md_file.read_text()
    blocks = []

    # Find all Python code blocks with their positions
    pattern = r"```python\n(.*?)```"
    for match in re.finditer(pattern, content, re.DOTALL):
        code = match.group(1)
        # Calculate line number
        line_num = content[: match.start()].count("\n") + 1
        blocks.append((code, line_num))

    return blocks


class TestInfrastructureDocs:
    """Test suite for infrastructure documentation quality."""

    def test_file_size_limits(self) -> None:
        """Ensure all infrastructure docs comply with line limits.

        Markdown files: 600 lines max (comprehensive documentation)
        Other files: 500 lines max (standard limit)
        """
        max_lines_md = 600
        max_lines_other = 500
        violations = []

        for md_file in get_infrastructure_docs() + get_playbook_docs():
            line_count = len(md_file.read_text().splitlines())
            # Use appropriate limit based on file type
            max_lines = max_lines_md if md_file.suffix == ".md" else max_lines_other
            if line_count > max_lines:
                violations.append(
                    f"{get_relative_path(md_file)}: {line_count} lines (max {max_lines})"
                )

        assert not violations, "File size violations found:\n" + "\n".join(violations)

    def test_python_code_syntax_valid(self) -> None:
        """Ensure all Python code blocks are syntactically valid."""
        errors = []

        for md_file in get_infrastructure_docs() + get_playbook_docs():
            blocks = extract_python_code_blocks(md_file)

            for code, line_num in blocks:
                # Skip code blocks that are intentionally partial examples
                if "..." in code or "# ..." in code:
                    continue

                try:
                    ast.parse(code)
                except SyntaxError as e:
                    errors.append(
                        f"{get_relative_path(md_file)}:{line_num}: Invalid Python syntax: {e}"
                    )

        assert not errors, "Python syntax errors found:\n" + "\n".join(errors)

    def test_cost_estimates_have_dates(self) -> None:
        """Ensure all cost estimates include 'as of' dates."""
        missing_dates = []

        date_pattern = re.compile(r"as of \d{4}-\d{2}", re.IGNORECASE)

        for md_file in get_infrastructure_docs():
            content = md_file.read_text()

            # Check if file discusses costs and verify it has date stamps
            if re.search(r"\$\d+", content) and not date_pattern.search(content):
                missing_dates.append(
                    f"{get_relative_path(md_file)}: "
                    "Contains cost info but missing 'as of YYYY-MM' date"
                )

        # This is a warning, not a hard failure
        if missing_dates:
            pytest.skip("Cost date warnings (non-blocking):\n" + "\n".join(missing_dates))

    def test_maintenance_schedules_present(self) -> None:
        """Ensure files with cost estimates have maintenance schedules."""
        missing_schedules = []

        schedule_pattern = re.compile(
            r"<!--\s*MAINTENANCE.*?REVIEW.*?-->", re.DOTALL | re.IGNORECASE
        )

        for md_file in get_infrastructure_docs():
            content = md_file.read_text()

            # If file has cost estimates, should have maintenance schedule
            if re.search(r"\$\d+", content) and not schedule_pattern.search(content):
                missing_schedules.append(
                    f"{get_relative_path(md_file)}: Has cost estimates but no maintenance schedule"
                )

        # Warning only
        if missing_schedules:
            pytest.skip("Maintenance schedule warnings:\n" + "\n".join(missing_schedules))

    def test_security_warnings_for_secrets(self) -> None:
        """Ensure docs mentioning API keys include security warnings."""
        missing_warnings = []

        secret_keywords = [
            "api_key",
            "apikey",
            "token",
            "secret",
            "credentials",
        ]
        warning_pattern = re.compile(
            r"(?:⚠️|warning|caution|security).*?(?:never|don't).*?(?:commit|hardcode|git)",
            re.IGNORECASE | re.DOTALL,
        )

        for md_file in get_infrastructure_docs() + get_playbook_docs():
            content = md_file.read_text()

            # Check if file mentions secrets
            has_secrets = any(keyword in content.lower() for keyword in secret_keywords)

            if has_secrets and not warning_pattern.search(content):
                # Check if it's in code examples (might be OK)
                code_blocks = extract_python_code_blocks(md_file)
                has_secret_in_code = any(
                    any(kw in code.lower() for kw in secret_keywords) for code, _ in code_blocks
                )

                if has_secret_in_code:
                    missing_warnings.append(
                        f"{get_relative_path(md_file)}: "
                        "Mentions secrets/tokens but no security warning"
                    )

        # Warning only, not blocking
        if missing_warnings:
            pytest.skip("Security warning suggestions:\n" + "\n".join(missing_warnings))
