"""
Dependency Manager Tool for Metis Agent CLI.

This tool provides comprehensive dependency management including analysis,
updates, security checks, and intelligent package management.
"""

import os
import json
import subprocess
import re
from datetime import datetime
from typing import Dict, List, Any, Optional, Tuple
from pathlib import Path

from .base import BaseTool


class DependencyManagerTool(BaseTool):
    """Tool for project dependency management and analysis."""
    
    def __init__(self):
        super().__init__()
    
    def can_handle(self, task: str) -> bool:
        """Check if this tool can handle the given task."""
        dependency_keywords = [
            "dependencies", "deps", "packages", "requirements", "node_modules",
            "package.json", "requirements.txt", "poetry", "pip", "npm", "yarn",
            "install", "update", "upgrade", "security", "vulnerabilities",
            "outdated", "unused", "version", "constraint"
        ]
        return any(keyword in task.lower() for keyword in dependency_keywords)
    
    def execute(self, task: str, **kwargs) -> Dict[str, Any]:
        """Execute dependency management operations."""
        try:
            cwd = kwargs.get("cwd", os.getcwd())
            
            # Detect project type
            project_type = self._detect_project_type(cwd)
            if not project_type:
                return {
                    "success": False,
                    "error": "No supported dependency files found",
                    "supported_files": ["requirements.txt", "package.json", "pyproject.toml", "Pipfile"]
                }
            
            task_lower = task.lower()
            
            if "check" in task_lower or "analyze" in task_lower:
                return self._analyze_dependencies(cwd, project_type)
            elif "update" in task_lower or "upgrade" in task_lower:
                return self._suggest_updates(cwd, project_type)
            elif "security" in task_lower or "vulnerabilities" in task_lower:
                return self._check_security(cwd, project_type)
            elif "unused" in task_lower:
                return self._find_unused_dependencies(cwd, project_type)
            elif "install" in task_lower or "add" in task_lower:
                return self._suggest_installation(task, cwd, project_type)
            else:
                return self._get_dependency_overview(cwd, project_type)
                
        except Exception as e:
            return {
                "success": False,
                "error": f"Dependency management failed: {str(e)}"
            }
    
    def _detect_project_type(self, cwd: str) -> Optional[str]:
        """Detect the type of project based on dependency files."""
        dependency_files = {
            "python": ["requirements.txt", "pyproject.toml", "Pipfile", "setup.py"],
            "node": ["package.json"],
            "poetry": ["pyproject.toml"],
            "pipenv": ["Pipfile"]
        }
        
        for project_type, files in dependency_files.items():
            for file in files:
                if os.path.exists(os.path.join(cwd, file)):
                    return project_type
        
        return None
    
    def _analyze_dependencies(self, cwd: str, project_type: str) -> Dict[str, Any]:
        """Analyze project dependencies comprehensively."""
        try:
            if project_type == "python":
                return self._analyze_python_dependencies(cwd)
            elif project_type == "node":
                return self._analyze_node_dependencies(cwd)
            elif project_type == "poetry":
                return self._analyze_poetry_dependencies(cwd)
            else:
                return {"success": False, "error": f"Unsupported project type: {project_type}"}
                
        except Exception as e:
            return {"success": False, "error": f"Analysis failed: {str(e)}"}
    
    def _analyze_python_dependencies(self, cwd: str) -> Dict[str, Any]:
        """Analyze Python dependencies."""
        dependencies = {}
        insights = []
        
        # Check requirements.txt
        req_file = os.path.join(cwd, "requirements.txt")
        if os.path.exists(req_file):
            with open(req_file, 'r') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        package_info = self._parse_requirement_line(line)
                        if package_info:
                            dependencies[package_info["name"]] = package_info
        
        # Check pyproject.toml
        pyproject_file = os.path.join(cwd, "pyproject.toml")
        if os.path.exists(pyproject_file):
            try:
                import toml
                with open(pyproject_file, 'r') as f:
                    data = toml.load(f)
                    
                # Poetry dependencies
                if "tool" in data and "poetry" in data["tool"]:
                    poetry_deps = data["tool"]["poetry"].get("dependencies", {})
                    for name, version in poetry_deps.items():
                        if name != "python":
                            dependencies[name] = {
                                "name": name,
                                "version": str(version),
                                "source": "pyproject.toml (poetry)"
                            }
                
                # PEP 621 dependencies
                if "project" in data:
                    proj_deps = data["project"].get("dependencies", [])
                    for dep in proj_deps:
                        package_info = self._parse_requirement_line(dep)
                        if package_info:
                            dependencies[package_info["name"]] = package_info
                            
            except ImportError:
                insights.append("- toml library not available for pyproject.toml parsing")
            except Exception as e:
                insights.append(f"- Error parsing pyproject.toml: {str(e)}")
        
        # Generate insights
        if dependencies:
            insights.extend([
                f"+ Found {len(dependencies)} dependencies",
                f"+ Project uses {'requirements.txt' if os.path.exists(req_file) else 'pyproject.toml'}",
            ])
            
            # Check for version constraints
            pinned = sum(1 for dep in dependencies.values() if "==" in dep.get("version", ""))
            if pinned == len(dependencies):
                insights.append("+ All dependencies have exact versions (fully pinned)")
            elif pinned > len(dependencies) / 2:
                insights.append("+ Most dependencies are pinned to specific versions")
            else:
                insights.append("- Many dependencies use flexible version constraints")
        
        return {
            "success": True,
            "project_type": "Python",
            "dependencies": dependencies,
            "total_count": len(dependencies),
            "insights": insights,
            "suggestions": self._get_python_suggestions(dependencies, cwd)
        }
    
    def _analyze_node_dependencies(self, cwd: str) -> Dict[str, Any]:
        """Analyze Node.js dependencies."""
        package_file = os.path.join(cwd, "package.json")
        
        try:
            with open(package_file, 'r') as f:
                package_data = json.load(f)
            
            dependencies = package_data.get("dependencies", {})
            dev_dependencies = package_data.get("devDependencies", {})
            
            all_deps = {**dependencies, **dev_dependencies}
            
            insights = [
                f"+ Found {len(dependencies)} runtime dependencies",
                f"+ Found {len(dev_dependencies)} development dependencies",
                f"+ Package name: {package_data.get('name', 'unnamed')}",
                f"+ Version: {package_data.get('version', 'unversioned')}"
            ]
            
            # Check for lock files
            if os.path.exists(os.path.join(cwd, "package-lock.json")):
                insights.append("+ npm lockfile found (package-lock.json)")
            if os.path.exists(os.path.join(cwd, "yarn.lock")):
                insights.append("+ Yarn lockfile found (yarn.lock)")
            
            return {
                "success": True,
                "project_type": "Node.js",
                "dependencies": dependencies,
                "dev_dependencies": dev_dependencies,
                "total_count": len(all_deps),
                "insights": insights,
                "suggestions": self._get_node_suggestions(package_data, cwd)
            }
            
        except json.JSONDecodeError:
            return {"success": False, "error": "Invalid package.json format"}
        except Exception as e:
            return {"success": False, "error": f"Node analysis failed: {str(e)}"}
    
    def _parse_requirement_line(self, line: str) -> Optional[Dict[str, str]]:
        """Parse a Python requirement line."""
        # Remove comments
        line = line.split('#')[0].strip()
        if not line:
            return None
        
        # Simple regex to parse package requirements
        match = re.match(r'^([a-zA-Z0-9\-_\.]+)\s*([><=!]+.*)?', line)
        if match:
            name = match.group(1)
            version = match.group(2) or ""
            return {
                "name": name,
                "version": version.strip(),
                "source": "requirements.txt"
            }
        
        return None
    
    def _suggest_updates(self, cwd: str, project_type: str) -> Dict[str, Any]:
        """Suggest dependency updates."""
        try:
            if project_type == "python":
                return self._check_python_updates(cwd)
            elif project_type == "node":
                return self._check_node_updates(cwd)
            else:
                return {"success": False, "error": f"Updates not supported for {project_type}"}
                
        except Exception as e:
            return {"success": False, "error": f"Update check failed: {str(e)}"}
    
    def _check_python_updates(self, cwd: str) -> Dict[str, Any]:
        """Check for Python package updates using pip."""
        try:
            result = subprocess.run(
                ["pip", "list", "--outdated", "--format=json"],
                cwd=cwd,
                capture_output=True,
                text=True,
                timeout=30
            )
            
            if result.returncode != 0:
                return {
                    "success": False,
                    "error": "Could not check for updates",
                    "suggestion": "Ensure pip is installed and working"
                }
            
            try:
                outdated = json.loads(result.stdout)
            except json.JSONDecodeError:
                outdated = []
            
            suggestions = []
            for package in outdated:
                suggestions.append({
                    "package": package["name"],
                    "current": package["version"],
                    "latest": package["latest_version"],
                    "command": f"pip install --upgrade {package['name']}"
                })
            
            return {
                "success": True,
                "outdated_count": len(outdated),
                "updates_available": suggestions,
                "insights": [
                    f"+ {len(outdated)} packages can be updated" if outdated else "+ All packages are up to date"
                ],
                "bulk_update_command": "pip install --upgrade " + " ".join(p["name"] for p in outdated) if outdated else None
            }
            
        except subprocess.TimeoutExpired:
            return {"success": False, "error": "Update check timed out"}
        except Exception as e:
            return {"success": False, "error": f"Update check failed: {str(e)}"}
    
    def _check_node_updates(self, cwd: str) -> Dict[str, Any]:
        """Check for Node.js package updates."""
        try:
            # Try npm outdated
            result = subprocess.run(
                ["npm", "outdated", "--json"],
                cwd=cwd,
                capture_output=True,
                text=True,
                timeout=30
            )
            
            try:
                outdated_data = json.loads(result.stdout) if result.stdout else {}
            except json.JSONDecodeError:
                outdated_data = {}
            
            suggestions = []
            for package, info in outdated_data.items():
                suggestions.append({
                    "package": package,
                    "current": info.get("current", "unknown"),
                    "wanted": info.get("wanted", "unknown"),
                    "latest": info.get("latest", "unknown"),
                    "command": f"npm install {package}@latest"
                })
            
            return {
                "success": True,
                "outdated_count": len(outdated_data),
                "updates_available": suggestions,
                "insights": [
                    f"+ {len(outdated_data)} packages can be updated" if outdated_data else "+ All packages are up to date"
                ],
                "bulk_update_command": "npm update" if outdated_data else None
            }
            
        except subprocess.TimeoutExpired:
            return {"success": False, "error": "Update check timed out"}
        except Exception as e:
            return {"success": False, "error": f"Update check failed: {str(e)}"}
    
    def _check_security(self, cwd: str, project_type: str) -> Dict[str, Any]:
        """Check for security vulnerabilities."""
        try:
            if project_type == "python":
                return self._check_python_security(cwd)
            elif project_type == "node":
                return self._check_node_security(cwd)
            else:
                return {"success": False, "error": f"Security check not supported for {project_type}"}
                
        except Exception as e:
            return {"success": False, "error": f"Security check failed: {str(e)}"}
    
    def _check_python_security(self, cwd: str) -> Dict[str, Any]:
        """Check Python packages for security vulnerabilities."""
        try:
            # Try to use pip-audit if available
            result = subprocess.run(
                ["pip-audit", "--format=json"],
                cwd=cwd,
                capture_output=True,
                text=True,
                timeout=60
            )
            
            if result.returncode == 0:
                try:
                    audit_data = json.loads(result.stdout)
                    vulnerabilities = audit_data.get("vulnerabilities", [])
                    
                    return {
                        "success": True,
                        "tool": "pip-audit",
                        "vulnerabilities_found": len(vulnerabilities),
                        "vulnerabilities": vulnerabilities,
                        "insights": [
                            f"+ Scanned for vulnerabilities using pip-audit",
                            f"+ {len(vulnerabilities)} vulnerabilities found" if vulnerabilities else "+ No vulnerabilities found"
                        ]
                    }
                except json.JSONDecodeError:
                    pass
            
            # Fallback suggestion
            return {
                "success": True,
                "tool": "manual",
                "suggestion": "Install pip-audit for security scanning: pip install pip-audit",
                "insights": [
                    "- pip-audit not available",
                    "- Manual security checking recommended"
                ]
            }
            
        except FileNotFoundError:
            return {
                "success": True,
                "tool": "manual", 
                "suggestion": "Install pip-audit for security scanning: pip install pip-audit",
                "insights": [
                    "- pip-audit not installed",
                    "- Consider using: pip install pip-audit"
                ]
            }
        except Exception as e:
            return {"success": False, "error": f"Security check failed: {str(e)}"}
    
    def _check_node_security(self, cwd: str) -> Dict[str, Any]:
        """Check Node.js packages for security vulnerabilities."""
        try:
            result = subprocess.run(
                ["npm", "audit", "--json"],
                cwd=cwd,
                capture_output=True,
                text=True,
                timeout=60
            )
            
            try:
                audit_data = json.loads(result.stdout)
                vulnerabilities = audit_data.get("vulnerabilities", {})
                
                # Count vulnerabilities by severity
                severity_counts = {"low": 0, "moderate": 0, "high": 0, "critical": 0}
                for vuln_name, vuln_data in vulnerabilities.items():
                    severity = vuln_data.get("severity", "unknown")
                    if severity in severity_counts:
                        severity_counts[severity] += 1
                
                total_vulns = sum(severity_counts.values())
                
                insights = [f"+ npm audit completed"]
                if total_vulns > 0:
                    insights.append(f"+ {total_vulns} vulnerabilities found")
                    for severity, count in severity_counts.items():
                        if count > 0:
                            insights.append(f"  - {count} {severity} severity")
                else:
                    insights.append("+ No vulnerabilities found")
                
                return {
                    "success": True,
                    "tool": "npm audit",
                    "total_vulnerabilities": total_vulns,
                    "severity_breakdown": severity_counts,
                    "fix_command": "npm audit fix" if total_vulns > 0 else None,
                    "insights": insights
                }
                
            except json.JSONDecodeError:
                # npm audit might return non-JSON output in some cases
                return {
                    "success": True,
                    "tool": "npm audit",
                    "raw_output": result.stdout,
                    "insights": ["+ npm audit completed (raw output available)"]
                }
                
        except Exception as e:
            return {"success": False, "error": f"Node security check failed: {str(e)}"}
    
    def _find_unused_dependencies(self, cwd: str, project_type: str) -> Dict[str, Any]:
        """Find potentially unused dependencies."""
        # This is a complex analysis that would require parsing all project files
        # For now, provide guidance on how to find unused dependencies
        
        if project_type == "python":
            return {
                "success": True,
                "tool_suggestion": "pip-autoremove",
                "command": "pip install pip-autoremove && pip-autoremove -L",
                "insights": [
                    "- Unused dependency detection requires code analysis",
                    "- Consider using pip-autoremove or similar tools",
                    "- Manual review of import statements recommended"
                ]
            }
        elif project_type == "node":
            return {
                "success": True,
                "tool_suggestion": "depcheck",
                "command": "npx depcheck",
                "insights": [
                    "- Use depcheck to find unused dependencies",
                    "- Run: npx depcheck",
                    "- Review results carefully before removing packages"
                ]
            }
        else:
            return {"success": False, "error": f"Unused dependency detection not supported for {project_type}"}
    
    def _suggest_installation(self, task: str, cwd: str, project_type: str) -> Dict[str, Any]:
        """Suggest how to install packages based on the task."""
        # Extract package names from task
        words = task.split()
        package_names = [word for word in words if not word.lower() in ["install", "add", "package", "dependency"]]
        
        if project_type == "python":
            commands = [f"pip install {' '.join(package_names)}"]
            if os.path.exists(os.path.join(cwd, "requirements.txt")):
                commands.append(f"echo '{' '.join(package_names)}' >> requirements.txt")
        elif project_type == "node":
            commands = [f"npm install {' '.join(package_names)}"]
        else:
            commands = ["# Package installation method depends on project type"]
        
        return {
            "success": True,
            "suggested_packages": package_names,
            "commands": commands,
            "project_type": project_type
        }
    
    def _get_dependency_overview(self, cwd: str, project_type: str) -> Dict[str, Any]:
        """Get a general overview of project dependencies."""
        analysis = self._analyze_dependencies(cwd, project_type)
        if not analysis.get("success"):
            return analysis
        
        # Add overview-specific information
        overview = {
            "success": True,
            "project_type": project_type,
            "summary": analysis.get("insights", []),
            "available_commands": [
                "Check updates: metis deps update", 
                "Security scan: metis deps security",
                "Find unused: metis deps unused",
                "Full analysis: metis deps check"
            ]
        }
        
        # Add dependency count info
        if "dependencies" in analysis:
            overview["dependency_count"] = analysis["total_count"]
        
        return overview
    
    def _get_python_suggestions(self, dependencies: Dict, cwd: str) -> List[str]:
        """Get Python-specific suggestions."""
        suggestions = []
        
        if not os.path.exists(os.path.join(cwd, "requirements.txt")):
            suggestions.append("Consider creating requirements.txt: pip freeze > requirements.txt")
        
        if len(dependencies) > 20:
            suggestions.append("Large number of dependencies - consider using virtual environments")
        
        # Check for common development packages
        dev_packages = ["pytest", "black", "flake8", "mypy"]
        missing_dev = [pkg for pkg in dev_packages if pkg not in dependencies]
        if missing_dev:
            suggestions.append(f"Consider adding dev tools: {', '.join(missing_dev)}")
        
        return suggestions
    
    def _get_node_suggestions(self, package_data: Dict, cwd: str) -> List[str]:
        """Get Node.js-specific suggestions."""
        suggestions = []
        
        if not package_data.get("scripts"):
            suggestions.append("Consider adding npm scripts for common tasks")
        
        if "dependencies" in package_data and "devDependencies" in package_data:
            if len(package_data["devDependencies"]) == 0:
                suggestions.append("Consider moving build tools to devDependencies")
        
        # Check for lock files
        if not os.path.exists(os.path.join(cwd, "package-lock.json")) and not os.path.exists(os.path.join(cwd, "yarn.lock")):
            suggestions.append("Consider committing package-lock.json for reproducible builds")
        
        return suggestions
