"""
Code Analyzer Utility for Code Assistant Bot
Author: RSK World (https://rskworld.in)
Founder: Molla Samser
Designer & Tester: Rima Khatun
Contact: help@rskworld.in, +91 93305 39277
Year: 2026
"""

import re
import ast
import json
from typing import Dict, List, Any

class CodeAnalyzer:
    """
    Utility class for analyzing code quality, complexity, and metrics
    """
    
    def __init__(self):
        self.complexity_thresholds = {
            'low': 5,
            'medium': 10,
            'high': 20
        }
    
    def analyze(self, code: str, language: str) -> Dict[str, Any]:
        """
        Analyze code and return comprehensive metrics
        """
        try:
            analysis = {
                'metrics': self._calculate_metrics(code, language),
                'complexity': self._analyze_complexity(code, language),
                'quality': self._assess_quality(code, language),
                'suggestions': self._generate_suggestions(code, language),
                'security': self._check_security_issues(code, language)
            }
            
            return analysis
            
        except Exception as e:
            return {
                'error': f'Analysis failed: {str(e)}',
                'metrics': {},
                'complexity': {},
                'quality': {},
                'suggestions': [],
                'security': []
            }
    
    def _calculate_metrics(self, code: str, language: str) -> Dict[str, int]:
        """
        Calculate basic code metrics
        """
        lines = code.split('\n')
        
        metrics = {
            'total_lines': len(lines),
            'code_lines': len([line for line in lines if line.strip() and not line.strip().startswith('#')]),
            'comment_lines': len([line for line in lines if line.strip().startswith('#')]),
            'blank_lines': len([line for line in lines if not line.strip()]),
            'functions': self._count_functions(code, language),
            'classes': self._count_classes(code, language),
            'imports': self._count_imports(code, language)
        }
        
        return metrics
    
    def _analyze_complexity(self, code: str, language: str) -> Dict[str, Any]:
        """
        Analyze code complexity
        """
        complexity = {
            'cyclomatic_complexity': self._calculate_cyclomatic_complexity(code, language),
            'cognitive_complexity': self._calculate_cognitive_complexity(code, language),
            'nesting_depth': self._calculate_nesting_depth(code, language),
            'complexity_level': 'low'
        }
        
        # Determine complexity level
        max_complexity = max(
            complexity['cyclomatic_complexity'],
            complexity['cognitive_complexity']
        )
        
        if max_complexity <= self.complexity_thresholds['low']:
            complexity['complexity_level'] = 'low'
        elif max_complexity <= self.complexity_thresholds['medium']:
            complexity['complexity_level'] = 'medium'
        else:
            complexity['complexity_level'] = 'high'
        
        return complexity
    
    def _assess_quality(self, code: str, language: str) -> Dict[str, Any]:
        """
        Assess code quality
        """
        quality = {
            'score': 0,
            'readability': self._assess_readability(code, language),
            'maintainability': self._assess_maintainability(code, language),
            'documentation': self._assess_documentation(code, language),
            'testing': self._assess_testing(code, language)
        }
        
        # Calculate overall quality score
        quality['score'] = (
            quality['readability'] * 0.3 +
            quality['maintainability'] * 0.3 +
            quality['documentation'] * 0.2 +
            quality['testing'] * 0.2
        )
        
        return quality
    
    def _generate_suggestions(self, code: str, language: str) -> List[str]:
        """
        Generate improvement suggestions
        """
        suggestions = []
        
        # Check for long lines
        lines = code.split('\n')
        long_lines = [i+1 for i, line in enumerate(lines) if len(line) > 80]
        if long_lines:
            suggestions.append(f"Consider breaking lines {long_lines[:3]} into shorter lines (max 80 characters)")
        
        # Check for missing docstrings (Python specific)
        if language == 'python':
            functions = self._extract_functions(code)
            for func in functions:
                if not func.get('docstring'):
                    suggestions.append(f"Function '{func['name']}' is missing a docstring")
        
        # Check for TODO/FIXME comments
        todo_patterns = [r'#\s*TODO', r'#\s*FIXME', r'//\s*TODO', r'//\s*FIXME']
        for pattern in todo_patterns:
            if re.search(pattern, code, re.IGNORECASE):
                suggestions.append("Code contains TODO/FIXME comments that need attention")
        
        # Check for duplicate code
        if self._has_duplicate_code(code):
            suggestions.append("Consider refactoring duplicate code into reusable functions")
        
        return suggestions
    
    def _check_security_issues(self, code: str, language: str) -> List[Dict[str, str]]:
        """
        Check for common security issues
        """
        security_issues = []
        
        # Check for hardcoded passwords/secrets
        secret_patterns = [
            r'password\s*=\s*["\'][^"\']+["\']',
            r'api_key\s*=\s*["\'][^"\']+["\']',
            r'secret\s*=\s*["\'][^"\']+["\']',
            r'token\s*=\s*["\'][^"\']+["\']'
        ]
        
        for pattern in secret_patterns:
            matches = re.finditer(pattern, code, re.IGNORECASE)
            for match in matches:
                security_issues.append({
                    'type': 'hardcoded_secret',
                    'severity': 'high',
                    'line': code[:match.start()].count('\n') + 1,
                    'description': 'Potential hardcoded secret detected'
                })
        
        # Check for SQL injection vulnerabilities
        sql_patterns = [
            r'execute\s*\(\s*["\'][^"\']*%[^"\']*["\']',
            r'query\s*\(\s*["\'][^"\']*%[^"\']*["\']'
        ]
        
        for pattern in sql_patterns:
            if re.search(pattern, code, re.IGNORECASE):
                security_issues.append({
                    'type': 'sql_injection',
                    'severity': 'high',
                    'description': 'Potential SQL injection vulnerability'
                })
        
        return security_issues
    
    def _count_functions(self, code: str, language: str) -> int:
        """Count number of functions in code"""
        if language == 'python':
            return len(re.findall(r'def\s+\w+\s*\(', code))
        elif language in ['javascript', 'typescript']:
            return len(re.findall(r'function\s+\w+\s*\(|const\s+\w+\s*=\s*\(', code))
        elif language == 'java':
            return len(re.findall(r'(public|private|protected)?\s*(static)?\s*\w+\s+\w+\s*\(', code))
        return 0
    
    def _count_classes(self, code: str, language: str) -> int:
        """Count number of classes in code"""
        if language == 'python':
            return len(re.findall(r'class\s+\w+', code))
        elif language in ['java', 'csharp', 'cpp']:
            return len(re.findall(r'class\s+\w+', code))
        elif language in ['javascript', 'typescript']:
            return len(re.findall(r'class\s+\w+', code))
        return 0
    
    def _count_imports(self, code: str, language: str) -> int:
        """Count number of import statements"""
        if language == 'python':
            return len(re.findall(r'^(import|from)\s+', code, re.MULTILINE))
        elif language in ['java', 'javascript', 'typescript']:
            return len(re.findall(r'^(import)\s+', code, re.MULTILINE))
        elif language == 'cpp':
            return len(re.findall(r'^#include', code, re.MULTILINE))
        return 0
    
    def _calculate_cyclomatic_complexity(self, code: str, language: str) -> int:
        """Calculate cyclomatic complexity"""
        complexity = 1  # Base complexity
        
        # Count decision points
        decision_patterns = [
            r'\bif\b', r'\belse\b', r'\belif\b', r'\bwhile\b', 
            r'\bfor\b', r'\bcase\b', r'\bcatch\b', r'\?'
        ]
        
        for pattern in decision_patterns:
            complexity += len(re.findall(pattern, code))
        
        return complexity
    
    def _calculate_cognitive_complexity(self, code: str, language: str) -> int:
        """Calculate cognitive complexity (simplified version)"""
        complexity = 0
        
        # Count nesting and control flow
        lines = code.split('\n')
        nesting_level = 0
        
        for line in lines:
            stripped = line.strip()
            
            # Increase nesting
            if any(keyword in stripped for keyword in ['if', 'for', 'while', 'try', 'with']):
                complexity += 1 + nesting_level
                nesting_level += 1
            elif any(keyword in stripped for keyword in ['else', 'elif', 'except', 'finally']):
                complexity += 1 + nesting_level
            
            # Decrease nesting
            if stripped.endswith(':') or '}' in stripped:
                nesting_level = max(0, nesting_level - 1)
        
        return complexity
    
    def _calculate_nesting_depth(self, code: str, language: str) -> int:
        """Calculate maximum nesting depth"""
        max_depth = 0
        current_depth = 0
        
        lines = code.split('\n')
        for line in lines:
            stripped = line.strip()
            
            if any(keyword in stripped for keyword in ['if', 'for', 'while', 'try', 'with']):
                current_depth += 1
                max_depth = max(max_depth, current_depth)
            elif any(keyword in stripped for keyword in ['else', 'elif', 'except', 'finally']):
                max_depth = max(max_depth, current_depth)
            elif '}' in stripped or stripped.endswith(':'):
                current_depth = max(0, current_depth - 1)
        
        return max_depth
    
    def _assess_readability(self, code: str, language: str) -> float:
        """Assess code readability (0-100 score)"""
        score = 100.0
        
        # Deduct for long lines
        lines = code.split('\n')
        long_lines = [line for line in lines if len(line) > 80]
        score -= min(20, len(long_lines) * 2)
        
        # Deduct for lack of comments
        comment_ratio = len([line for line in lines if line.strip().startswith('#')]) / len(lines)
        if comment_ratio < 0.1:
            score -= 15
        
        # Deduct for complex naming
        if re.search(r'[a-z]\d+[A-Z]', code):  # CamelCase with numbers
            score -= 10
        
        return max(0, score)
    
    def _assess_maintainability(self, code: str, language: str) -> float:
        """Assess code maintainability (0-100 score)"""
        score = 100.0
        
        # Deduct for high complexity
        complexity = self._calculate_cyclomatic_complexity(code, language)
        if complexity > 10:
            score -= min(30, (complexity - 10) * 3)
        
        # Deduct for long functions
        if language == 'python':
            functions = self._extract_functions(code)
            for func in functions:
                if func['length'] > 50:
                    score -= 10
        
        return max(0, score)
    
    def _assess_documentation(self, code: str, language: str) -> float:
        """Assess code documentation (0-100 score)"""
        score = 100.0
        
        lines = code.split('\n')
        total_lines = len([line for line in lines if line.strip()])
        comment_lines = len([line for line in lines if line.strip().startswith('#')])
        
        if total_lines > 0:
            comment_ratio = comment_lines / total_lines
            if comment_ratio < 0.1:
                score -= 50
            elif comment_ratio < 0.2:
                score -= 25
        
        return max(0, score)
    
    def _assess_testing(self, code: str, language: str) -> float:
        """Assess testing coverage (simplified)"""
        score = 100.0
        
        # Check for test-related keywords
        test_patterns = [
            r'test_', r'spec_', r'describe\(', r'it\(', r'assert', r'expect'
        ]
        
        has_tests = any(re.search(pattern, code, re.IGNORECASE) for pattern in test_patterns)
        if not has_tests:
            score -= 100
        
        return max(0, score)
    
    def _extract_functions(self, code: str) -> List[Dict[str, Any]]:
        """Extract function information (Python specific)"""
        functions = []
        
        try:
            tree = ast.parse(code)
            for node in ast.walk(tree):
                if isinstance(node, ast.FunctionDef):
                    functions.append({
                        'name': node.name,
                        'line': node.lineno,
                        'length': node.end_lineno - node.lineno if hasattr(node, 'end_lineno') else 0,
                        'docstring': ast.get_docstring(node) is not None
                    })
        except:
            pass
        
        return functions
    
    def _has_duplicate_code(self, code: str) -> bool:
        """Check for duplicate code blocks (simplified)"""
        lines = [line.strip() for line in code.split('\n') if line.strip()]
        
        # Simple check for repeated lines
        line_counts = {}
        for line in lines:
            if len(line) > 10:  # Ignore short lines
                line_counts[line] = line_counts.get(line, 0) + 1
        
        # If any line appears more than twice, consider it duplicate
        return any(count > 2 for count in line_counts.values())
