import ast
import os
import re
from typing import List

from bastion.analyzers.python_analyzer import PythonAnalyzer
from bastion.analyzers.js_analyzer import JavaScriptAnalyzer
from bastion.analyzers.base import BaseAnalyzer
from bastion.models import Capability, ScanResult

ANALYZERS = {
    '.py': PythonAnalyzer,
    '.js': JavaScriptAnalyzer,
    '.ts': JavaScriptAnalyzer,
    '.jsx': JavaScriptAnalyzer,
    '.tsx': JavaScriptAnalyzer,
}

def get_analyzer(file_extension):
    return ANALYZERS.get(file_extension)


def analyze_plugin(plugin, timestamp: str) -> ScanResult:
    all_capabilities: List[Capability] = []
    dependencies: set = set()
    skills: list = []
    description: str = ""
    file_count: int = 0
    total_lines: int = 0

    if plugin.type == "folder":
        for root, dirs, files in os.walk(plugin.path):
            for fname in sorted(files):
                ext = os.path.splitext(fname)[1].lower()
                if ext in ANALYZERS:
                    file_count += 1
                    fpath = os.path.join(root, fname)
                    analyzer_cls = ANALYZERS[ext]
                    analyzer = analyzer_cls()
                    all_capabilities.extend(analyzer.analyze(fpath))
                    _extract_metadata(fpath, ext, dependencies, skills, file_count == 1)
                    try:
                        with open(fpath, 'r', encoding='utf-8', errors='replace') as f:
                            total_lines += sum(1 for _ in f)
                    except Exception:
                        pass
                    if not description and file_count == 1:
                        description = _extract_description(fpath, ext)
    else:
        ext = os.path.splitext(plugin.path)[1].lower()
        file_count = 1
        if ext in ANALYZERS:
            analyzer_cls = ANALYZERS[ext]
            analyzer = analyzer_cls()
            all_capabilities.extend(analyzer.analyze(plugin.path))
            _extract_metadata(plugin.path, ext, dependencies, skills, True)
            try:
                with open(plugin.path, 'r', encoding='utf-8', errors='replace') as f:
                    total_lines += sum(1 for _ in f)
            except Exception:
                pass
            description = _extract_description(plugin.path, ext)

    severity_order = {"Low": 0, "Medium": 1, "High": 2, "Critical": 3}
    risk_level = "Low"
    for cap in all_capabilities:
        if severity_order.get(cap.severity, 0) > severity_order.get(risk_level, 0):
            risk_level = cap.severity

    return ScanResult(
        plugin_id=plugin.id,
        capabilities=all_capabilities,
        risk_level=risk_level,
        timestamp=timestamp,
        dependencies=sorted(dependencies),
        skills=skills,
        description=description,
        file_count=file_count,
        total_lines=total_lines,
    )


STDLIB_MODULES = {
    'os', 'sys', 're', 'json', 'math', 'time', 'datetime', 'collections',
    'itertools', 'functools', 'pathlib', 'typing', 'abc', 'io', 'hashlib',
    'uuid', 'logging', 'copy', 'string', 'textwrap', 'enum', 'dataclasses',
    'contextlib', 'ast', 'inspect', 'unittest', 'argparse', 'configparser',
    'csv', 'sqlite3', 'threading', 'multiprocessing', 'queue', 'struct',
    'base64', 'hmac', 'secrets', 'tempfile', 'shutil', 'glob', 'fnmatch',
    'stat', 'fileinput', 'pickle', 'shelve', 'marshal', 'dbm', 'gzip',
    'bz2', 'lzma', 'zipfile', 'tarfile', 'zlib', 'pprint', 'dis',
    'traceback', 'warnings', 'weakref', 'types', 'operator', 'codecs',
    'unicodedata', 'locale', 'gettext', 'bisect', 'heapq', 'array',
    'decimal', 'fractions', 'random', 'statistics', 'cmath',
    'socket', 'ssl', 'select', 'signal', 'subprocess', 'ctypes',
    'http', 'urllib', 'email', 'html', 'xml', 'webbrowser',
    '__future__', 'builtins', 'importlib', 'pkgutil', 'platform',
}

NODE_BUILTINS = {
    'fs', 'path', 'os', 'http', 'https', 'url', 'util', 'crypto',
    'stream', 'events', 'child_process', 'cluster', 'net', 'dns',
    'tls', 'zlib', 'buffer', 'querystring', 'assert', 'readline',
}


def _extract_metadata(file_path: str, ext: str, dependencies: set, skills: list, is_first: bool):
    try:
        with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
            source = f.read()
    except Exception:
        return

    if ext == '.py':
        try:
            tree = ast.parse(source, filename=file_path)
        except SyntaxError:
            return

        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    top = alias.name.split('.')[0]
                    if top not in STDLIB_MODULES and not top.startswith('_'):
                        dependencies.add(top)
            elif isinstance(node, ast.ImportFrom):
                if node.module:
                    top = node.module.split('.')[0]
                    if top not in STDLIB_MODULES and not top.startswith('_') and node.level == 0:
                        dependencies.add(top)
            elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                if not node.name.startswith('_') and node.col_offset == 0:
                    skills.append(node.name)
            elif isinstance(node, ast.ClassDef):
                if not node.name.startswith('_') and node.col_offset == 0:
                    skills.append(node.name)
    else:
        for line in source.splitlines():
            m = re.search(r'''require\s*\(\s*['"]([^'"./][^'"]*)['"]\s*\)''', line)
            if m:
                pkg = m.group(1).split('/')[0]
                if pkg not in NODE_BUILTINS:
                    dependencies.add(pkg)
            m = re.search(r'''from\s+['"]([^'"./][^'"]*)['"]\s*''', line)
            if m:
                pkg = m.group(1).split('/')[0]
                if pkg not in NODE_BUILTINS:
                    dependencies.add(pkg)
            m = re.match(r'export\s+(?:default\s+)?(?:async\s+)?(?:function|class)\s+(\w+)', line)
            if m:
                skills.append(m.group(1))
            m = re.match(r'module\.exports\s*=\s*(\w+)', line)
            if m:
                skills.append(m.group(1))


def _extract_description(file_path: str, ext: str) -> str:
    try:
        with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
            source = f.read()
    except Exception:
        return ""

    if ext == '.py':
        try:
            tree = ast.parse(source, filename=file_path)
            docstring = ast.get_docstring(tree)
            if docstring:
                first_line = docstring.strip().split('\n')[0]
                return first_line[:200]
        except SyntaxError:
            pass
    else:
        lines = source.splitlines()
        for line in lines[:10]:
            stripped = line.strip()
            if stripped.startswith('//') and len(stripped) > 4:
                return stripped[2:].strip()[:200]
            if stripped.startswith('/*') or stripped.startswith('/**'):
                comment = stripped.lstrip('/* ').rstrip('*/').strip()
                if comment:
                    return comment[:200]
            if stripped and not stripped.startswith(('import', 'from', 'require', "'use", '"use', 'const', 'let', 'var', '//')):
                break

    return ""
