import os
import json
import logging
from typing import List, Dict, Set
import aiohttp
from .detectors.ecosystem import EcosystemDetector
from .parsers.base import Dependency, BaseParser
from .parsers.npm import NpmParser
from .parsers.python import PythonParser
from .parsers.go import GoParser
from .parsers.rust import RustParser
from .parsers.ruby import RubyParser
from .parsers.php import PhpParser
from .parsers.maven import MavenParser
from .parsers.dotnet import DotnetParser
from .parsers.long_tail import LongTailParser
from .registry.manager import RegistryManager
from .utils.registry_discovery import PrivateRegistryDetector
from .utils.cicd_discovery import CiCdDetector
from .scoring.engine import RiskScorer
from .utils.poc_generator import PocGenerator
from .utils.ui import UI

log = logging.getLogger("dep-raptor")

class ScanEngine:
    def __init__(self):
        self.parsers: List[BaseParser] = [
            NpmParser(),
            PythonParser(),
            GoParser(),
            RustParser(),
            RubyParser(),
            PhpParser(),
            MavenParser(),
            DotnetParser(),
            LongTailParser()
        ]
        self.local_package_names: Set[str] = set()

    async def run(self, root_path: str, poc: bool = False):
        UI.info(f"Scanning {root_path} for ecosystems...")
        ecosystems = EcosystemDetector.detect(root_path)
        UI.success(f"Detected ecosystems: [bold cyan]{', '.join(ecosystems)}[/bold cyan]")

        all_dependencies: List[Dependency] = []
        UI.info("Parsing dependencies...")
        for root, _, files in os.walk(root_path):
            # Skip noise
            if "node_modules" in root or ".git" in root:
                continue

            for file in files:
                file_path = os.path.join(root, file)
                for parser in self.parsers:
                    # Generic extension check for dotnet
                    is_match = False
                    if file in parser.get_markers():
                        is_match = True
                    elif any(file.endswith(m) for m in parser.get_markers() if m.startswith('.')):
                        is_match = True

                    if is_match:
                        log.info(f"Parsing {file_path}")
                        deps = parser.parse(file_path)
                        all_dependencies.extend(deps)
                        
                        # Collect local package name for npm
                        if file == "package.json":
                            try:
                                with open(file_path, 'r') as f:
                                    data = json.load(f)
                                    if "name" in data:
                                        self.local_package_names.add(data["name"])
                                        log.debug(f"Discovered local package: {data['name']}")
                            except Exception:
                                pass

        if not all_dependencies:
            UI.warning("No dependencies found.")
            return

        UI.success(f"Found [bold cyan]{len(all_dependencies)}[/bold cyan] dependencies")
        
        UI.info("Checking registries (10 threads)...")
        async with aiohttp.ClientSession() as session:
            registry_manager = RegistryManager(session)
            vulnerabilities = []
            
            # Detect project-wide context
            npm_scopes = PrivateRegistryDetector.get_npm_private_scopes(root_path)
            cicd_tools = CiCdDetector.detect_in_tree(root_path)
            cicd_present = len(cicd_tools) > 0

            for dep in all_dependencies:
                # 1. Skip if already resolved locally or via git
                if dep.is_resolved_locally or dep.is_git_dependency:
                    continue
                
                # 2. Skip if it's a local package name (monorepo support)
                if dep.name in self.local_package_names:
                    log.debug(f"Skipping {dep.name} - found as local package in repository")
                    continue
                
                # 3. Skip if it's an npm scoped package and the scope is in .npmrc
                if dep.ecosystem == "npm" and dep.name.startswith("@"):
                    scope = dep.name.split('/')[0][1:]
                    if scope in npm_scopes:
                        log.debug(f"Skipping {dep.name} - scope @{scope} found in .npmrc")
                        continue

                # 3. Check if it exists in public registry
                exists = await registry_manager.package_exists(dep.ecosystem, dep.name)
                
                # 4. If it does NOT exist, it's a potential dependency confusion
                if not exists:
                    severity = RiskScorer.calculate(dep, cicd_present)
                    vulnerabilities.append((dep, severity))
                    
            if poc and vulnerabilities:
                UI.safety_warning()
                if input("\nType YES to continue, anything else to abort: ").lower() == "yes":
                    for dep, severity in vulnerabilities:
                        PocGenerator.generate(dep.name, dep.ecosystem, "results")
                    UI.success(f"Generated {len(vulnerabilities)} PoC packages")

            UI.print_summary(len(all_dependencies), len(vulnerabilities), len(vulnerabilities) if poc else 0, "results")
            UI.print_vulnerabilities(vulnerabilities)
