"""Extract and type-check Python examples from documentation.

This module validates that all Python code examples in infrastructure
documentation are syntactically valid and pass type checking.
"""

import argparse
import logging
import re
import subprocess
import sys
import tempfile
from pathlib import Path

logger = logging.getLogger(__name__)


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

    Args:
        md_file: Path to markdown file

    Returns:
        List of tuples (code_block, line_number) where the code block starts
    """
    content = md_file.read_text()
    blocks = []

    pattern = r"```python\n(.*?)```"
    for match in re.finditer(pattern, content, re.DOTALL):
        code = match.group(1)
        line_num = content[: match.start()].count("\n") + 1
        blocks.append((code, line_num))

    return blocks


def validate_with_ty(code: str, source_file: Path, line_num: int) -> tuple[bool, str]:
    """Validate code block with ty type checking.

    Args:
        code: Python code to validate
        source_file: Original markdown file (for error reporting)
        line_num: Line number where code block starts

    Returns:
        Tuple of (is_valid, error_message)
    """
    # Skip partial examples
    if "..." in code or "# ..." in code:
        return (True, "")

    # Add common imports that might be missing from examples
    common_imports = """
import os
import sys
from typing import Any, Optional
from pathlib import Path
"""

    full_code = common_imports + "\n" + code

    with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f:
        f.write(full_code)
        temp_path = Path(f.name)

    timeout_seconds = 30
    commands = [["uvx", "ty", "check", str(temp_path)], ["ty", "check", str(temp_path)]]
    cmd_used: list[str] = []

    try:
        # Try uvx first (for CI and users with uv), fall back to ty directly.
        # The else clause executes only if no command succeeded (all raised exceptions).
        for cmd in commands:
            try:
                result = subprocess.run(
                    cmd,
                    capture_output=True,
                    text=True,
                    timeout=timeout_seconds,
                )
                cmd_used = cmd
                logger.debug("Type checking with: %s", " ".join(cmd[:2]))
                break  # Command executed (even if it found type errors)
            except FileNotFoundError:
                logger.debug("Command not found: %s, trying fallback", cmd[0])
                continue
            except PermissionError as e:
                logger.debug("Permission denied for %s: %s, trying fallback", cmd[0], e)
                continue
        else:
            # Neither uvx nor ty found
            return (False, "ty not found - install with: uvx ty or pip install ty")

        if result.returncode != 0:
            # Adjust line numbers in error messages
            adjusted_errors = []
            # Combine stdout and stderr for ty output
            output = result.stdout + result.stderr
            for line in output.splitlines():
                if temp_path.name in line:
                    # Parse ty error format: file.py:10:5: error message
                    match = re.match(rf".*{re.escape(temp_path.name)}:(\d+):(\d+:)?(.*)", line)
                    if match:
                        temp_line = int(match.group(1))
                        # Subtract common imports offset
                        actual_line = temp_line - common_imports.count("\n")
                        if actual_line > 0:
                            adjusted_line = line_num + actual_line - 1
                            error_msg = match.group(3) if match.group(3) else ""
                            adjusted_errors.append(f"{source_file}:{adjusted_line}:{error_msg}")
                    else:
                        adjusted_errors.append(line)

            return (False, "\n".join(adjusted_errors))

        return (True, "")

    except subprocess.TimeoutExpired:
        cmd_name = " ".join(cmd_used[:2]) if cmd_used else "ty"
        return (
            False,
            f"{source_file}:{line_num}: {cmd_name} timeout after {timeout_seconds}s",
        )
    finally:
        temp_path.unlink(missing_ok=True)


# Backwards compatibility alias
validate_with_mypy = validate_with_ty


def validate_documentation(docs_path: Path, strict: bool = False) -> tuple[int, int]:
    """Validate all Python examples in documentation.

    Args:
        docs_path: Path to documentation directory
        strict: If True, fail on any type errors; if False, only syntax errors

    Returns:
        Tuple of (total_blocks, failed_blocks)
    """
    total_blocks = 0
    failed_blocks = 0

    for md_file in docs_path.rglob("*.md"):
        blocks = extract_python_blocks(md_file)

        for code, line_num in blocks:
            total_blocks += 1

            if strict:
                is_valid, error = validate_with_ty(code, md_file, line_num)
                if not is_valid:
                    print(f"❌ Type error in {md_file}:{line_num}")
                    if error:
                        print(error)
                    print()
                    failed_blocks += 1
            else:
                # Just check syntax
                # Skip code blocks that are intentionally partial examples
                if "..." in code or "# ..." in code:
                    continue

                try:
                    compile(code, f"<{md_file}:{line_num}>", "exec")
                except SyntaxError as e:
                    print(f"❌ Syntax error in {md_file}:{line_num}")
                    print(f"   {e}")
                    print()
                    failed_blocks += 1

    return total_blocks, failed_blocks


def main() -> int:
    """Main entry point."""
    parser = argparse.ArgumentParser(description="Validate Python code examples in documentation")
    parser.add_argument(
        "docs_path",
        type=Path,
        nargs="?",
        default=Path(".claude/knowledge/infrastructure"),
        help="Path to documentation directory (default: .claude/knowledge/infrastructure)",
    )
    parser.add_argument(
        "--strict",
        action="store_true",
        help="Enable type checking with ty (default: syntax only)",
    )

    args = parser.parse_args()

    if not args.docs_path.exists():
        print(f"❌ Path not found: {args.docs_path}", file=sys.stderr)
        return 1

    print(f"Validating Python examples in {args.docs_path}")
    if args.strict:
        print("Mode: Type checking with ty")
    else:
        print("Mode: Syntax validation only")
    print()

    total, failed = validate_documentation(args.docs_path, args.strict)

    print(f"\n{'=' * 60}")
    print(f"Total code blocks: {total}")
    print(f"Failed: {failed}")
    if total > 0:
        print(f"Success rate: {(total - failed) / total * 100:.1f}%")

    if failed > 0:
        print("\n❌ Validation failed")
        return 1

    print("\n✅ All code examples are valid")
    return 0


if __name__ == "__main__":
    sys.exit(main())
