"""
Generated Code Detection - Identify auto-generated files.

[20251226_FEATURE] Pro tier feature for crawl_project.

Detects auto-generated files that should be skipped or marked:
- Protobuf: *_pb2.py, *_pb2_grpc.py
- GraphQL: generated types
- ORM migrations: Django/Alembic migrations
- Build artifacts: dist/, build/, __pycache__/
- Lockfiles: package-lock.json, poetry.lock

Usage:
    from code_scalpel.analysis.generated_code import detect_generated_files

    result = detect_generated_files("/path/to/project")
    for gf in result.generated_files:
        print(f"{gf.path} ({gf.gen_type})")
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path


@dataclass
class GeneratedFileInfo:
    """Information about a detected generated file."""

    path: str  # Relative path from project root
    gen_type: str  # Type of generation (protobuf, migration, etc.)
    confidence: float  # 0.0 - 1.0
    reason: str  # Why it was detected as generated


@dataclass
class GeneratedCodeResult:
    """Result of generated code detection."""

    generated_files: list[GeneratedFileInfo]
    generated_dirs: list[str]
    summary: dict[str, int]  # Count by gen_type
    total_generated: int


class GeneratedCodeDetector:
    """Detects auto-generated code in a project."""

    # File patterns that indicate generated code
    FILE_PATTERNS: list[tuple[str, str, float]] = [
        # (pattern, gen_type, confidence)
        (r".*_pb2\.py$", "protobuf", 0.99),
        (r".*_pb2_grpc\.py$", "protobuf_grpc", 0.99),
        (r".*\.pb\.go$", "protobuf_go", 0.99),
        (r".*_generated\.py$", "generated", 0.95),
        (r".*_generated\.go$", "generated", 0.95),
        (r".*\.generated\.(ts|js)$", "generated", 0.95),
        (r".*\.g\.dart$", "generated_dart", 0.95),
        (r"migrations/\d+_.*\.py$", "django_migration", 0.90),
        (r"versions/[a-f0-9]+_.*\.py$", "alembic_migration", 0.90),
        (r".*\.min\.js$", "minified", 0.99),
        (r".*\.bundle\.js$", "bundled", 0.95),
        (r".*\.d\.ts$", "typescript_declarations", 0.85),
        (r"swagger\.json$", "openapi_generated", 0.80),
        (r"openapi\.json$", "openapi_generated", 0.80),
    ]

    # Directories that typically contain generated code
    GENERATED_DIRS: set[str] = {
        "__pycache__",
        "dist",
        "build",
        ".next",
        ".nuxt",
        "node_modules",
        "coverage",
        "htmlcov",
        ".pytest_cache",
        ".mypy_cache",
        ".tox",
        "target",  # Rust/Java
        "vendor",  # Go
        ".eggs",
        "*.egg-info",
        "__pypackages__",
        ".turbo",
        ".vercel",
        ".netlify",
    }

    # Header patterns that indicate generated files
    HEADER_PATTERNS: list[tuple[str, str]] = [
        (r"# Generated by", "header_generated"),
        (r"// Code generated .* DO NOT EDIT", "header_go_generated"),
        (r"// AUTO-GENERATED", "header_auto"),
        (r"// This file is auto-generated", "header_auto"),
        (r"# This file is automatically @generated", "header_generated"),
        (r"/* eslint-disable */", "eslint_disabled"),  # Often in generated
        (r"# -*- coding: .* generated", "header_generated"),
    ]

    def __init__(self, project_root: str | Path):
        """Initialize detector with project root."""
        self.root = Path(project_root)

    def detect(self, check_headers: bool = True, max_files: int = 10000) -> GeneratedCodeResult:
        """
        Detect generated files in the project.

        Args:
            check_headers: Whether to read file headers (slower but more accurate)
            max_files: Maximum files to scan

        Returns:
            GeneratedCodeResult with all detected generated files
        """
        generated_files: list[GeneratedFileInfo] = []
        generated_dirs: list[str] = []
        file_count = 0

        # First, identify generated directories
        for item in self.root.iterdir():
            if item.is_dir():
                dir_name = item.name
                if dir_name in self.GENERATED_DIRS or any(
                    dir_name.endswith(pattern.replace("*", "")) for pattern in self.GENERATED_DIRS if "*" in pattern
                ):
                    generated_dirs.append(str(item.relative_to(self.root)))

        # Scan files
        for file_path in self.root.rglob("*"):
            if file_count >= max_files:
                break

            if not file_path.is_file():
                continue

            file_count += 1
            rel_path = str(file_path.relative_to(self.root))

            # Skip files in known generated directories
            if any(rel_path.startswith(gd + "/") or rel_path.startswith(gd + "\\") for gd in generated_dirs):
                continue

            # Check file patterns
            for pattern, gen_type, confidence in self.FILE_PATTERNS:
                if re.match(pattern, rel_path):
                    generated_files.append(
                        GeneratedFileInfo(
                            path=rel_path,
                            gen_type=gen_type,
                            confidence=confidence,
                            reason=f"Matched pattern: {pattern}",
                        )
                    )
                    break
            else:
                # Check file headers if enabled
                if check_headers and file_path.suffix in (
                    ".py",
                    ".js",
                    ".ts",
                    ".go",
                    ".java",
                ):
                    header_match = self._check_header(file_path)
                    if header_match:
                        generated_files.append(
                            GeneratedFileInfo(
                                path=rel_path,
                                gen_type=header_match[1],
                                confidence=0.85,
                                reason=f"Header matched: {header_match[0]}",
                            )
                        )

        # Build summary
        summary: dict[str, int] = {}
        for gf in generated_files:
            summary[gf.gen_type] = summary.get(gf.gen_type, 0) + 1

        return GeneratedCodeResult(
            generated_files=generated_files,
            generated_dirs=generated_dirs,
            summary=summary,
            total_generated=len(generated_files) + len(generated_dirs),
        )

    def _check_header(self, file_path: Path, max_lines: int = 10) -> tuple[str, str] | None:
        """Check file header for generated code patterns."""
        try:
            with open(file_path, encoding="utf-8", errors="ignore") as f:
                for i, line in enumerate(f):
                    if i >= max_lines:
                        break
                    for pattern, gen_type in self.HEADER_PATTERNS:
                        if re.search(pattern, line, re.IGNORECASE):
                            return (pattern, gen_type)
        except Exception:
            pass
        return None


def detect_generated_files(
    project_root: str | Path, check_headers: bool = True, max_files: int = 10000
) -> GeneratedCodeResult:
    """Convenience function to detect generated files."""
    detector = GeneratedCodeDetector(project_root)
    return detector.detect(check_headers=check_headers, max_files=max_files)
