"""JavaScript-specific analysis including minified code handling.

Handles:
- Minified/bundled JS detection and basic unminification
- JS-specific dangerous patterns
- Node.js specific risks
"""
import re
from typing import List, Tuple

from .patterns import Finding, Severity


def is_minified_js(content: str) -> bool:
    """Detect if JavaScript content is minified."""
    lines = content.split('\n')
    
    # If file has very few lines but lots of content, likely minified
    if len(lines) < 10 and len(content) > 5000:
        return True
    
    # Check average line length (minified = very long lines)
    non_empty_lines = [l for l in lines if l.strip()]
    if non_empty_lines:
        avg_length = sum(len(l) for l in non_empty_lines) / len(non_empty_lines)
        if avg_length > 500:
            return True
    
    # Check for lack of whitespace (minified removes most whitespace)
    if len(content) > 1000:
        whitespace_ratio = content.count(' ') / len(content)
        if whitespace_ratio < 0.05:
            return True
    
    return False


def basic_unminify(content: str) -> str:
    """
    Basic unminification to make patterns more detectable.
    This is NOT a full beautifier, just enough to expose dangerous patterns.
    """
    # Add newlines after common statement endings
    content = re.sub(r';(?=[^\s])', ';\n', content)
    content = re.sub(r'\{(?=[^\s])', '{\n', content)
    content = re.sub(r'\}(?=[^\s])', '}\n', content)
    
    # Add spaces around operators to expose patterns
    content = re.sub(r'([=<>!]+)', r' \1 ', content)
    
    return content


def analyze_javascript(content: str, filename: str) -> List[Finding]:
    """Analyze JavaScript code for dangerous patterns."""
    findings = []
    original_content = content
    
    # Check if minified
    minified = is_minified_js(content)
    if minified:
        findings.append(Finding(
            severity=Severity.WARN,
            pattern_id='minified_javascript',
            description='Minified JavaScript detected - harder to audit, may hide malicious code',
            file=filename,
            line=1,
            match='<minified>',
            context=f'File size: {len(content)} bytes, few lines',
        ))
        # Unminify for analysis
        content = basic_unminify(content)
    
    lines = content.split('\n')
    
    # JS-specific patterns that need context
    js_patterns = [
        # Dynamic code execution
        (r'\beval\s*\(', 'js_eval', Severity.DANGER, 'eval() in JavaScript'),
        (r'new\s+Function\s*\(', 'js_function_constructor', Severity.DANGER, 'Function constructor (eval equivalent)'),
        (r'setTimeout\s*\(\s*["\'][^"\']*["\']', 'js_settimeout_string', Severity.DANGER, 'setTimeout with string (implicit eval)'),
        (r'setInterval\s*\(\s*["\'][^"\']*["\']', 'js_setinterval_string', Severity.DANGER, 'setInterval with string (implicit eval)'),
        
        # Dangerous Node.js APIs
        (r'child_process', 'js_child_process', Severity.WARN, 'child_process module - command execution'),
        (r'\.exec\s*\(', 'js_exec', Severity.WARN, 'exec() call - may be command execution'),
        (r'\.execSync\s*\(', 'js_exec_sync', Severity.DANGER, 'execSync() - synchronous command execution'),
        (r'require\s*\(\s*["\']child_process["\']', 'js_require_child_process', Severity.DANGER, 'Requiring child_process module'),
        
        # Prototype pollution
        (r'__proto__', 'js_proto_access', Severity.WARN, '__proto__ access - prototype pollution risk'),
        (r'constructor\s*\[\s*["\']prototype["\']', 'js_constructor_proto', Severity.DANGER, 'Prototype access via constructor'),
        (r'Object\.assign\s*\([^,]+,\s*[^)]+\)', 'js_object_assign', Severity.INFO, 'Object.assign - check for prototype pollution'),
        
        # DOM XSS sinks
        (r'\.innerHTML\s*=', 'js_innerhtml', Severity.WARN, 'innerHTML assignment - XSS risk'),
        (r'\.outerHTML\s*=', 'js_outerhtml', Severity.WARN, 'outerHTML assignment - XSS risk'),
        (r'document\.write\s*\(', 'js_document_write', Severity.WARN, 'document.write - XSS risk'),
        (r'\.insertAdjacentHTML\s*\(', 'js_insert_html', Severity.WARN, 'insertAdjacentHTML - XSS risk'),
        
        # Dangerous URL handling
        (r'location\s*=|location\.href\s*=', 'js_location_assign', Severity.WARN, 'Location assignment - open redirect risk'),
        (r'window\.open\s*\(', 'js_window_open', Severity.INFO, 'window.open - popup/redirect'),
        
        # Fetch/XHR to unknown destinations
        (r'fetch\s*\(\s*[^"\'`]', 'js_fetch_dynamic', Severity.WARN, 'Fetch with dynamic URL'),
        (r'XMLHttpRequest', 'js_xhr', Severity.INFO, 'XMLHttpRequest usage'),
        
        # Credential access patterns
        (r'localStorage\.getItem\s*\([^)]*(?:token|key|secret|password|credential)', 'js_localstorage_cred', Severity.WARN, 'Reading credentials from localStorage'),
        (r'sessionStorage\.getItem\s*\([^)]*(?:token|key|secret|password|credential)', 'js_sessionstorage_cred', Severity.WARN, 'Reading credentials from sessionStorage'),
        (r'document\.cookie', 'js_cookie_access', Severity.INFO, 'Cookie access'),
        
        # WebSocket (may exfiltrate data)
        (r'new\s+WebSocket\s*\(', 'js_websocket', Severity.INFO, 'WebSocket connection'),
        
        # Obfuscation indicators
        (r'\\x[0-9a-fA-F]{2}(\\x[0-9a-fA-F]{2}){5,}', 'js_hex_string', Severity.WARN, 'Hex-encoded string in JS'),
        (r'\\u[0-9a-fA-F]{4}(\\u[0-9a-fA-F]{4}){5,}', 'js_unicode_string', Severity.WARN, 'Unicode-encoded string in JS'),
        (r'atob\s*\(', 'js_atob', Severity.WARN, 'Base64 decoding (atob)'),
        (r'String\.fromCharCode\s*\([^)]{20,}\)', 'js_fromcharcode', Severity.WARN, 'String.fromCharCode - potential obfuscation'),
    ]
    
    for pattern, pattern_id, severity, description in js_patterns:
        for match in re.finditer(pattern, content, re.IGNORECASE):
            # Find line number in potentially unminified content
            line_num = content[:match.start()].count('\n') + 1
            
            # Get context
            if line_num <= len(lines):
                context = lines[line_num - 1].strip()[:200]
            else:
                context = match.group(0)[:100]
            
            # Note if found in minified code
            if minified:
                description += ' (found in minified code)'
            
            findings.append(Finding(
                severity=severity,
                pattern_id=pattern_id,
                description=description,
                file=filename,
                line=line_num,
                match=match.group(0)[:100],
                context=context,
            ))
    
    return findings


def is_javascript_file(filename: str) -> bool:
    """Check if a file is JavaScript."""
    return filename.endswith(('.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx'))
