"""
Code Analysis Tool for Metis Agent.

This module provides comprehensive code analysis capabilities including
parsing code structure, detecting patterns, and analyzing dependencies.
"""
import ast
import os
import re
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
import importlib.util

from .base import BaseTool


class CodeAnalysisTool(BaseTool):
    """Tool for analyzing code structure, patterns, and dependencies."""
    
    def __init__(self):
        # Language-specific analyzers
        self.supported_languages = {
            '.py': 'python',
            '.js': 'javascript',
            '.jsx': 'javascript',
            '.ts': 'typescript',
            '.tsx': 'typescript',
            '.java': 'java',
            '.cpp': 'cpp',
            '.c': 'c',
            '.cs': 'csharp',
            '.go': 'go',
            '.rs': 'rust',
            '.rb': 'ruby',
            '.php': 'php'
        }
        
    def can_handle(self, task: str) -> bool:
        """Check if this tool can handle the given task."""
        code_keywords = [
            'analyze', 'code', 'function', 'class', 'method', 'structure',
            'dependencies', 'imports', 'complexity', 'patterns', 'parse',
            'ast', 'syntax', 'review', 'quality', 'metrics', 'documentation'
        ]
        task_lower = task.lower()
        return any(keyword in task_lower for keyword in code_keywords)
    
    def execute(self, task: str) -> Any:
        """Execute code analysis based on the task."""
        task_lower = task.lower()
        
        if 'analyze' in task_lower:
            # Try to extract filename from task
            words = task.split()
            for word in words:
                if '.' in word and not word.startswith('.') and any(word.endswith(ext) for ext in self.supported_languages):
                    return self.analyze_file(word)
            # If no specific file, analyze current directory
            return self.analyze_project(".")
        
        elif 'structure' in task_lower or 'overview' in task_lower:
            return self.get_code_overview(".")
            
        elif 'dependencies' in task_lower or 'imports' in task_lower:
            return self.analyze_dependencies(".")
            
        else:
            # Default to project analysis
            return self.analyze_project(".")
    
    def analyze_file(self, file_path: str) -> Dict[str, Any]:
        """Analyze a specific code file."""
        try:
            path = Path(file_path)
            if not path.exists():
                return {"error": f"File '{file_path}' not found"}
            
            if not path.is_file():
                return {"error": f"'{file_path}' is not a file"}
            
            file_ext = path.suffix.lower()
            if file_ext not in self.supported_languages:
                return {"error": f"Unsupported file type: {file_ext}"}
            
            language = self.supported_languages[file_ext]
            
            with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            
            analysis = {
                "success": True,
                "file_path": str(path),
                "language": language,
                "size_bytes": len(content.encode('utf-8')),
                "lines_count": len(content.splitlines()),
                "character_count": len(content)
            }
            
            # Language-specific analysis
            if language == 'python':
                analysis.update(self._analyze_python_file(content, path))
            elif language in ['javascript', 'typescript']:
                analysis.update(self._analyze_javascript_file(content, path))
            else:
                analysis.update(self._analyze_generic_file(content, path))
            
            return analysis
            
        except Exception as e:
            return {"error": f"Failed to analyze file: {str(e)}"}
    
    def _analyze_python_file(self, content: str, path: Path) -> Dict[str, Any]:
        """Analyze Python-specific code structure."""
        analysis = {
            "functions": [],
            "classes": [],
            "imports": [],
            "docstrings": [],
            "complexity_estimate": "low"
        }
        
        try:
            # Parse AST
            tree = ast.parse(content)
            
            for node in ast.walk(tree):
                if isinstance(node, ast.FunctionDef):
                    func_info = {
                        "name": node.name,
                        "line_number": node.lineno,
                        "args_count": len(node.args.args),
                        "is_async": isinstance(node, ast.AsyncFunctionDef),
                        "decorators": [d.id if isinstance(d, ast.Name) else str(d) for d in node.decorator_list]
                    }
                    if ast.get_docstring(node):
                        func_info["has_docstring"] = True
                    analysis["functions"].append(func_info)
                
                elif isinstance(node, ast.ClassDef):
                    class_info = {
                        "name": node.name,
                        "line_number": node.lineno,
                        "methods": [n.name for n in node.body if isinstance(n, ast.FunctionDef)],
                        "base_classes": [base.id if isinstance(base, ast.Name) else str(base) for base in node.bases]
                    }
                    if ast.get_docstring(node):
                        class_info["has_docstring"] = True
                    analysis["classes"].append(class_info)
                
                elif isinstance(node, (ast.Import, ast.ImportFrom)):
                    if isinstance(node, ast.Import):
                        for alias in node.names:
                            analysis["imports"].append({
                                "type": "import",
                                "module": alias.name,
                                "alias": alias.asname,
                                "line": node.lineno
                            })
                    else:  # ImportFrom
                        for alias in node.names:
                            analysis["imports"].append({
                                "type": "from_import",
                                "module": node.module,
                                "name": alias.name,
                                "alias": alias.asname,
                                "line": node.lineno
                            })
            
            # Estimate complexity
            total_nodes = len(list(ast.walk(tree)))
            if total_nodes > 200:
                analysis["complexity_estimate"] = "high"
            elif total_nodes > 100:
                analysis["complexity_estimate"] = "medium"
            
            analysis["ast_nodes_count"] = total_nodes
            
        except SyntaxError as e:
            analysis["syntax_error"] = f"Line {e.lineno}: {e.msg}"
        except Exception as e:
            analysis["parse_error"] = str(e)
        
        return analysis
    
    def _analyze_javascript_file(self, content: str, path: Path) -> Dict[str, Any]:
        """Analyze JavaScript/TypeScript file structure."""
        analysis = {
            "functions": [],
            "classes": [],
            "imports": [],
            "exports": [],
            "complexity_estimate": "low"
        }
        
        lines = content.splitlines()
        
        # Simple pattern matching for JS/TS structures
        function_pattern = re.compile(r'^\s*(export\s+)?(async\s+)?function\s+(\w+)', re.MULTILINE)
        arrow_function_pattern = re.compile(r'^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>', re.MULTILINE)
        class_pattern = re.compile(r'^\s*(export\s+)?class\s+(\w+)', re.MULTILINE)
        import_pattern = re.compile(r'^\s*import\s+(.+?)\s+from\s+[\'"](.+?)[\'"]', re.MULTILINE)
        export_pattern = re.compile(r'^\s*export\s+(.+)', re.MULTILINE)
        
        # Find functions
        for match in function_pattern.finditer(content):
            analysis["functions"].append({
                "name": match.group(3),
                "type": "function",
                "is_exported": bool(match.group(1)),
                "is_async": bool(match.group(2))
            })
        
        for match in arrow_function_pattern.finditer(content):
            analysis["functions"].append({
                "name": match.group(1),
                "type": "arrow_function",
                "is_exported": "export" in match.group(0)
            })
        
        # Find classes
        for match in class_pattern.finditer(content):
            analysis["classes"].append({
                "name": match.group(2),
                "is_exported": bool(match.group(1))
            })
        
        # Find imports
        for match in import_pattern.finditer(content):
            analysis["imports"].append({
                "what": match.group(1).strip(),
                "from": match.group(2),
                "type": "es6_import"
            })
        
        # Find exports
        for match in export_pattern.finditer(content):
            analysis["exports"].append(match.group(1).strip())
        
        # Estimate complexity based on various factors
        complexity_indicators = len(re.findall(r'\bif\b|\bfor\b|\bwhile\b|\btry\b|\bcatch\b', content))
        if complexity_indicators > 20:
            analysis["complexity_estimate"] = "high"
        elif complexity_indicators > 10:
            analysis["complexity_estimate"] = "medium"
        
        analysis["complexity_indicators"] = complexity_indicators
        
        return analysis
    
    def _analyze_generic_file(self, content: str, path: Path) -> Dict[str, Any]:
        """Generic analysis for unsupported languages."""
        lines = content.splitlines()
        
        return {
            "functions": [],
            "classes": [],
            "imports": [],
            "comment_lines": len([line for line in lines if line.strip().startswith(('#', '//', '/*', '*'))]),
            "blank_lines": len([line for line in lines if not line.strip()]),
            "code_lines": len([line for line in lines if line.strip() and not line.strip().startswith(('#', '//', '/*', '*'))]),
            "complexity_estimate": "unknown"
        }
    
    def analyze_project(self, directory: str = ".") -> Dict[str, Any]:
        """Analyze an entire project directory."""
        try:
            dir_path = Path(directory).resolve()
            if not dir_path.exists():
                return {"error": f"Directory '{directory}' does not exist"}
            
            analysis = {
                "success": True,
                "directory": str(dir_path),
                "languages": {},
                "total_files": 0,
                "total_lines": 0,
                "files_by_language": {},
                "complexity_summary": {"low": 0, "medium": 0, "high": 0},
                "common_imports": {},
                "project_patterns": []
            }
            
            # Recursively analyze all code files
            for file_path in dir_path.rglob("*"):
                if file_path.is_file() and file_path.suffix.lower() in self.supported_languages:
                    language = self.supported_languages[file_path.suffix.lower()]
                    
                    file_analysis = self.analyze_file(str(file_path))
                    if file_analysis.get("success"):
                        analysis["total_files"] += 1
                        analysis["total_lines"] += file_analysis.get("lines_count", 0)
                        
                        # Track languages
                        if language not in analysis["languages"]:
                            analysis["languages"][language] = 0
                        analysis["languages"][language] += 1
                        
                        # Track files by language
                        if language not in analysis["files_by_language"]:
                            analysis["files_by_language"][language] = []
                        analysis["files_by_language"][language].append(str(file_path.relative_to(dir_path)))
                        
                        # Track complexity
                        complexity = file_analysis.get("complexity_estimate", "low")
                        analysis["complexity_summary"][complexity] = analysis["complexity_summary"].get(complexity, 0) + 1
                        
                        # Track common imports (Python specific for now)
                        if language == "python" and "imports" in file_analysis:
                            for imp in file_analysis["imports"]:
                                module = imp.get("module", "")
                                if module:
                                    if module not in analysis["common_imports"]:
                                        analysis["common_imports"][module] = 0
                                    analysis["common_imports"][module] += 1
            
            # Identify common patterns
            analysis["project_patterns"] = self._identify_project_patterns(dir_path, analysis)
            
            return analysis
            
        except Exception as e:
            return {"error": f"Failed to analyze project: {str(e)}"}
    
    def get_code_overview(self, directory: str = ".") -> Dict[str, Any]:
        """Get a high-level overview of the codebase."""
        analysis = self.analyze_project(directory)
        
        if not analysis.get("success"):
            return analysis
        
        # Create a concise overview
        overview = {
            "success": True,
            "summary": {
                "primary_language": max(analysis["languages"].items(), key=lambda x: x[1])[0] if analysis["languages"] else None,
                "total_files": analysis["total_files"],
                "total_lines": analysis["total_lines"],
                "languages_used": list(analysis["languages"].keys()),
                "complexity_distribution": analysis["complexity_summary"],
                "most_common_imports": dict(sorted(analysis["common_imports"].items(), key=lambda x: x[1], reverse=True)[:10]),
                "patterns": analysis["project_patterns"]
            }
        }
        
        return overview
    
    def analyze_dependencies(self, directory: str = ".") -> Dict[str, Any]:
        """Analyze project dependencies and imports."""
        try:
            dir_path = Path(directory).resolve()
            analysis = self.analyze_project(directory)
            
            if not analysis.get("success"):
                return analysis
            
            dependencies = {
                "success": True,
                "internal_imports": set(),
                "external_imports": set(),
                "import_graph": {},
                "unused_imports": [],
                "missing_imports": []
            }
            
            # Analyze imports for each language
            for file_path in dir_path.rglob("*.py"):
                if file_path.is_file():
                    file_analysis = self.analyze_file(str(file_path))
                    if file_analysis.get("success") and "imports" in file_analysis:
                        rel_path = str(file_path.relative_to(dir_path))
                        dependencies["import_graph"][rel_path] = []
                        
                        for imp in file_analysis["imports"]:
                            module = imp.get("module", "")
                            if module:
                                # Check if it's an internal import
                                if module.startswith('.') or (dir_path / f"{module.replace('.', '/')}.py").exists():
                                    dependencies["internal_imports"].add(module)
                                else:
                                    dependencies["external_imports"].add(module)
                                
                                dependencies["import_graph"][rel_path].append(module)
            
            # Convert sets to sorted lists for JSON serialization
            dependencies["internal_imports"] = sorted(list(dependencies["internal_imports"]))
            dependencies["external_imports"] = sorted(list(dependencies["external_imports"]))
            
            return dependencies
            
        except Exception as e:
            return {"error": f"Failed to analyze dependencies: {str(e)}"}
    
    def _identify_project_patterns(self, dir_path: Path, analysis: Dict[str, Any]) -> List[str]:
        """Identify common patterns in the project structure."""
        patterns = []
        
        # Check for common directory structures
        common_dirs = ["src", "lib", "tests", "test", "docs", "examples", "scripts"]
        for dirname in common_dirs:
            if (dir_path / dirname).exists():
                patterns.append(f"Has {dirname}/ directory")
        
        # Check for configuration files
        config_files = ["setup.py", "package.json", "requirements.txt", "Dockerfile", ".gitignore"]
        for config_file in config_files:
            if (dir_path / config_file).exists():
                patterns.append(f"Has {config_file}")
        
        # Analyze language patterns
        if "python" in analysis["languages"]:
            if (dir_path / "__init__.py").exists() or any(p.name == "__init__.py" for p in dir_path.rglob("__init__.py")):
                patterns.append("Python package structure")
        
        if "javascript" in analysis["languages"]:
            if (dir_path / "package.json").exists():
                patterns.append("Node.js project")
        
        # Check for testing patterns
        test_files = list(dir_path.rglob("*test*.py")) + list(dir_path.rglob("test_*.py"))
        if test_files:
            patterns.append(f"Has {len(test_files)} test files")
        
        return patterns
