"""
Codebase Indexer - Builds comprehensive index of the entire codebase
"""

import ast
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional


@dataclass
class FileInfo:
    """Information about a source file"""

    path: Path
    relative_path: str
    size: int
    lines: int
    language: str
    content: Optional[str] = None
    functions: list[str] = field(default_factory=list)
    classes: list[str] = field(default_factory=list)
    imports: list[str] = field(default_factory=list)


@dataclass
class CodebaseIndex:
    """Complete index of the codebase"""

    root_path: Path
    files: dict[str, FileInfo] = field(default_factory=dict)
    functions: dict[str, list[str]] = field(default_factory=dict)  # function_name -> [file_paths]
    classes: dict[str, list[str]] = field(default_factory=dict)  # class_name -> [file_paths]
    imports: dict[str, set[str]] = field(default_factory=dict)  # module -> {files_that_import_it}
    total_files: int = 0
    total_lines: int = 0
    total_size: int = 0


class CodebaseIndexer:
    """
    Indexes the entire codebase for the Oracle Agent

    Extracts:
    - File structure and contents
    - Function and class definitions
    - Import relationships
    - Documentation strings
    """

    SUPPORTED_EXTENSIONS = {
        ".py": "python",
        ".js": "javascript",
        ".ts": "typescript",
        ".jsx": "javascript",
        ".tsx": "typescript",
        ".md": "markdown",
        ".txt": "text",
        ".yaml": "yaml",
        ".yml": "yaml",
        ".json": "json",
        ".toml": "toml",
    }

    IGNORE_DIRS = {
        "__pycache__",
        ".git",
        ".venv",
        "venv",
        "node_modules",
        "dist",
        "build",
        ".pytest_cache",
        ".egg-info",
        "rlm_old_backup",
        ".vscode",
        ".idea",
        "logs",
        "agent_logs",
    }

    def __init__(self, root_path: Path):
        """Initialize indexer with repository root"""
        self.root_path = Path(root_path).resolve()
        self.index = CodebaseIndex(root_path=self.root_path)

    def build_index(self, include_content: bool = True) -> CodebaseIndex:
        """
        Build complete codebase index

        Args:
            include_content: Whether to include full file contents in index

        Returns:
            CodebaseIndex with all extracted information
        """
        print(f"🔍 Indexing codebase at: {self.root_path}")

        # Walk directory tree
        for root, dirs, files in os.walk(self.root_path):
            # Filter out ignored directories
            dirs[:] = [d for d in dirs if d not in self.IGNORE_DIRS]

            root_path = Path(root)

            for file in files:
                file_path = root_path / file
                ext = file_path.suffix.lower()

                if ext in self.SUPPORTED_EXTENSIONS:
                    self._index_file(file_path, include_content)

        print(f"✅ Indexed {self.index.total_files} files ({self.index.total_lines:,} lines)")
        return self.index

    def _index_file(self, file_path: Path, include_content: bool):
        """Index a single file"""
        try:
            relative_path = str(file_path.relative_to(self.root_path))

            # Read file content
            content = file_path.read_text(encoding="utf-8", errors="ignore")
            lines = content.count("\n") + 1
            size = file_path.stat().st_size

            ext = file_path.suffix.lower()
            language = self.SUPPORTED_EXTENSIONS[ext]

            # Create file info
            file_info = FileInfo(
                path=file_path,
                relative_path=relative_path,
                size=size,
                lines=lines,
                language=language,
                content=content if include_content else None,
            )

            # Extract Python-specific metadata
            if language == "python":
                self._extract_python_info(file_info, content)

            # Store in index
            self.index.files[relative_path] = file_info
            self.index.total_files += 1
            self.index.total_lines += lines
            self.index.total_size += size

        except Exception as e:
            print(f"⚠️  Error indexing {file_path}: {e}")

    def _extract_python_info(self, file_info: FileInfo, content: str):
        """Extract Python-specific information using AST"""
        try:
            tree = ast.parse(content)

            for node in ast.walk(tree):
                # Extract function definitions
                if isinstance(node, ast.FunctionDef):
                    func_name = node.name
                    file_info.functions.append(func_name)

                    if func_name not in self.index.functions:
                        self.index.functions[func_name] = []
                    self.index.functions[func_name].append(file_info.relative_path)

                # Extract class definitions
                elif isinstance(node, ast.ClassDef):
                    class_name = node.name
                    file_info.classes.append(class_name)

                    if class_name not in self.index.classes:
                        self.index.classes[class_name] = []
                    self.index.classes[class_name].append(file_info.relative_path)

                # Extract imports
                elif isinstance(node, (ast.Import, ast.ImportFrom)):
                    if isinstance(node, ast.Import):
                        for alias in node.names:
                            module = alias.name
                            file_info.imports.append(module)

                            if module not in self.index.imports:
                                self.index.imports[module] = set()
                            self.index.imports[module].add(file_info.relative_path)

                    elif isinstance(node, ast.ImportFrom):
                        module = node.module or ""
                        if module:
                            file_info.imports.append(module)

                            if module not in self.index.imports:
                                self.index.imports[module] = set()
                            self.index.imports[module].add(file_info.relative_path)

        except SyntaxError:
            # Skip files with syntax errors
            pass

    def get_file_info(self, relative_path: str) -> Optional[FileInfo]:
        """Get information about a specific file"""
        return self.index.files.get(relative_path)

    def find_function(self, function_name: str) -> list[str]:
        """Find all files containing a function"""
        return self.index.functions.get(function_name, [])

    def find_class(self, class_name: str) -> list[str]:
        """Find all files containing a class"""
        return self.index.classes.get(class_name, [])

    def find_importers(self, module_name: str) -> set[str]:
        """Find all files that import a module"""
        return self.index.imports.get(module_name, set())

    def search_files(self, pattern: str) -> list[FileInfo]:
        """Search for files matching a pattern"""
        results = []
        for relative_path, file_info in self.index.files.items():
            if pattern.lower() in relative_path.lower():
                results.append(file_info)
        return results

    def get_summary(self) -> str:
        """Get a summary of the indexed codebase"""
        summary = [
            "Codebase Summary",
            "=" * 80,
            f"Root: {self.index.root_path}",
            f"Total Files: {self.index.total_files}",
            f"Total Lines: {self.index.total_lines:,}",
            f"Total Size: {self.index.total_size / 1024:.1f} KB",
            "",
            f"Functions: {len(self.index.functions)}",
            f"Classes: {len(self.index.classes)}",
            f"Modules: {len(self.index.imports)}",
        ]

        # Language breakdown
        lang_counts = {}
        for file_info in self.index.files.values():
            lang = file_info.language
            lang_counts[lang] = lang_counts.get(lang, 0) + 1

        summary.append("")
        summary.append("Files by Language:")
        for lang, count in sorted(lang_counts.items(), key=lambda x: -x[1]):
            summary.append(f"  {lang}: {count}")

        return "\n".join(summary)
