"""
Phase 3: Codebase Indexing

This module implements a 5-layer codebase indexing system for the Brynq execution engine.
It gives agents deep context into the structure, dependencies, and history of a repository.

Layers:
1. File Tree Index (gitignore-aware)
2. Symbol Extraction (AST-based)
3. Dependency Graph (import resolution)
4. Git History (blame, churn)
5. Project Context (brynq.yaml / CLAUDE.md)
"""

import os
import ast
import subprocess
from pathlib import Path
from typing import Dict, List, Any

class CodebaseIndexer:
    def __init__(self, repo_root: str):
        self.repo_root = Path(repo_root).resolve()

    # --- Layer 1: File Tree Index ---
    def get_file_tree(self) -> List[str]:
        """Return a list of tracked files, respecting .gitignore."""
        try:
            # The most reliable way to get a gitignore-aware tree is via git itself
            result = subprocess.run(
                ["git", "ls-files"], 
                cwd=self.repo_root, 
                capture_output=True, 
                text=True, 
                check=True
            )
            return [f for f in result.stdout.split("\n") if f.strip()]
        except Exception:
            # Fallback to standard os.walk if not a git repo
            files = []
            for root, dirs, filenames in os.walk(self.repo_root):
                # Simple ignore rules for fallback
                if '.git' in dirs: dirs.remove('.git')
                if '__pycache__' in dirs: dirs.remove('__pycache__')
                if 'node_modules' in dirs: dirs.remove('node_modules')
                
                for filename in filenames:
                    path = os.path.relpath(os.path.join(root, filename), self.repo_root)
                    files.append(path)
            return files

    # --- Layer 2: Symbol Extraction ---
    def extract_symbols(self, file_path: str) -> Dict[str, Any]:
        """Extract classes and functions from a Python file using AST."""
        full_path = self.repo_root / file_path
        if not full_path.exists() or not str(full_path).endswith(".py"):
            return {"error": "File not found or not a Python file"}

        try:
            with open(full_path, "r", encoding="utf-8") as f:
                tree = ast.parse(f.read(), filename=str(full_path))
            
            symbols = {"classes": [], "functions": []}
            for node in ast.walk(tree):
                if isinstance(node, ast.ClassDef):
                    symbols["classes"].append({
                        "name": node.name,
                        "line": node.lineno
                    })
                elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
                    symbols["functions"].append({
                        "name": node.name,
                        "line": node.lineno
                    })
            return symbols
        except SyntaxError as e:
            return {"error": f"Syntax error: {e}"}
        except Exception as e:
            return {"error": str(e)}

    # --- Layer 3: Dependency Graph ---
    def get_dependencies(self, file_path: str) -> List[str]:
        """Extract import statements to map dependencies (Python only for MVP)."""
        full_path = self.repo_root / file_path
        if not full_path.exists() or not str(full_path).endswith(".py"):
            return []

        try:
            with open(full_path, "r", encoding="utf-8") as f:
                tree = ast.parse(f.read(), filename=str(full_path))
            
            imports = []
            for node in ast.walk(tree):
                if isinstance(node, ast.Import):
                    for alias in node.names:
                        imports.append(alias.name)
                elif isinstance(node, ast.ImportFrom):
                    if node.module:
                        imports.append(node.module)
            return list(set(imports))
        except:
            return []

    # --- Layer 4: Git History Context ---
    def get_file_history(self, file_path: str) -> Dict[str, Any]:
        """Get git churn and recent authors for a file."""
        try:
            # Get commit count (churn)
            churn_res = subprocess.run(
                ["git", "log", "--oneline", "--", file_path],
                cwd=self.repo_root, capture_output=True, text=True
            )
            churn = len([line for line in churn_res.stdout.split("\n") if line.strip()])
            
            # Get last modified date and author
            last_commit = subprocess.run(
                ["git", "log", "-1", "--format=%cd|%an", "--", file_path],
                cwd=self.repo_root, capture_output=True, text=True
            ).stdout.strip()
            
            if not last_commit:
                return {"churn": 0, "last_modified": "Unknown", "last_author": "Unknown"}
                
            date, author = last_commit.split("|", 1)
            return {"churn": churn, "last_modified": date, "last_author": author}
        except:
            return {"error": "Git not available or file not tracked"}

    # --- Layer 5: Project Context ---
    def get_project_context(self) -> str:
        """Read brynq.yaml, CLAUDE.md, or project README.md for high-level instructions."""
        priority_files = ["brynq.yaml", "CLAUDE.md", "README.md"]
        for pf in priority_files:
            path = self.repo_root / pf
            if path.exists():
                try:
                    with open(path, "r", encoding="utf-8") as f:
                        return f"--- {pf} ---\n{f.read()[:2000]}..." # Truncate to save context
                except:
                    pass
        return "No high-level project context file found."

    # --- Full Summary ---
    def get_full_context_summary(self, target_file: str) -> Dict[str, Any]:
        """Aggregate all layers into a single context object for the LLM."""
        return {
            "project_context": self.get_project_context(),
            "target_file_symbols": self.extract_symbols(target_file),
            "target_file_dependencies": self.get_dependencies(target_file),
            "target_file_history": self.get_file_history(target_file),
        }
