from __future__ import annotations

import concurrent.futures
from pathlib import Path
from typing import Iterable, List, Optional, Tuple

from .config_loader import FerruccioConfig, load_config
from .filesystem import cleanup_temp_dir, collect_stats, extract_zip_if_needed, iter_files, safe_read_text
from .js_analyzer import JSAnalyzer
from .metadata import detect_plugin_metadata
from .models import AttackSurfaceEntry, PluginMetadata, PluginReport, ProjectReport, Stats
from .php_analyzer import PHPAnalyzer
from .vuln_feed import load_vuln_db, match_vulnerabilities


class Scanner:
    def __init__(
        self,
        strict: bool = True,
        include_suspicious: bool = False,
        wp_version: str = "latest",
        php_version: str = "8.2",
        config_path: Path | None = None,
        vuln_db: Path | None = None,
    ):
        base_config = FerruccioConfig(strict=strict, include_suspicious=include_suspicious)
        file_config = load_config(config_path)
        merged = FerruccioConfig.merge(base_config, file_config)
        # CLI vuln_db overrides config file
        if vuln_db:
            merged.vuln_db = vuln_db

        self.strict = merged.strict if merged.strict is not None else strict
        self.include_suspicious = merged.include_suspicious if merged.include_suspicious is not None else include_suspicious
        self.wp_version = wp_version
        self.php_version = php_version
        self.config = merged
        self.vuln_advisories = load_vuln_db(merged.vuln_db) if merged.vuln_db and merged.vuln_db.exists() else []

    def scan_path(self, target: Path) -> PluginReport:
        workdir, temp_dir = extract_zip_if_needed(target)
        try:
            metadata, meta_file = detect_plugin_metadata(iter_files(workdir, ["*.php"], ignore=self.config.ignore_paths))
            warnings: List[str] = []
            if not metadata.name:
                warnings.append("Plugin metadata not detected; header may be missing")
            if meta_file:
                warnings.append(f"Metadata source: {meta_file}")
            file_types, total_files, code_bytes, loc_php, loc_js, loc_css = collect_stats(workdir, ignore=self.config.ignore_paths)
            stats = Stats(
                total_files=total_files,
                code_bytes=code_bytes,
                file_types=file_types,
                loc_php=loc_php,
                loc_js=loc_js,
                loc_css=loc_css,
            )
            
            # PHP Analysis
            php_analyzer = PHPAnalyzer(
                self.wp_version,
                self.php_version,
                self.strict,
                self.include_suspicious,
                ignore_paths=self.config.ignore_paths,
                allow_sinks=self.config.allow_sinks,
                deny_sinks={sink: "Config-defined sink" for sink in self.config.deny_sinks},
                severity_threshold=self.config.severity_threshold,
            )
            findings, attack_surface = php_analyzer.analyze(workdir)

            # JS Analysis
            js_analyzer = JSAnalyzer(strict=self.strict)
            for js_file in (p for p in workdir.rglob("*.js") if p.is_file()):
                # Skip minified files (heuristic)
                if js_file.name.endswith(".min.js"):
                    continue
                # Check ignore paths
                # Note: path_is_ignored is in filesystem.py, but we need to import it or use the one from php_analyzer if we move it.
                # For now, let's assume iter_files logic or similar. 
                # Actually, let's just use the same ignore logic.
                from .filesystem import path_is_ignored
                if path_is_ignored(js_file, self.config.ignore_paths):
                    continue

                content = safe_read_text(js_file, limit=300_000)
                js_findings, js_surface = js_analyzer.analyze(js_file, content)
                findings.extend(js_findings)
                attack_surface.extend(js_surface)

            if self.vuln_advisories:
                findings.extend(match_vulnerabilities(metadata, self.vuln_advisories))

            report = PluginReport(
                plugin_path=str(target),
                resolved_path=str(workdir),
                metadata=metadata,
                wp_version=self.wp_version,
                php_version=self.php_version,
                stats=stats,
                findings=findings,
                attack_surface=attack_surface,
                warnings=warnings,
            )
            report._update_score()
            return report
        finally:
            cleanup_temp_dir(temp_dir)

    def scan_plugins_dir(self, plugins_root: Path, threads: int = 4) -> ProjectReport:
        plugin_paths = self._discover_plugins(plugins_root)
        reports: List[PluginReport] = []
        with concurrent.futures.ThreadPoolExecutor(max_workers=max(1, threads)) as executor:
            futures = [executor.submit(self.scan_path, path) for path in plugin_paths]
            for f in concurrent.futures.as_completed(futures):
                reports.append(f.result())
        project = ProjectReport(target=str(plugins_root), plugin_reports=reports)
        project.compute_summary()
        return project

    def _discover_plugins(self, plugins_root: Path) -> List[Path]:
        plugins_root = plugins_root.expanduser().resolve()
        if (plugins_root / "wp-content" / "plugins").exists():
            plugins_root = plugins_root / "wp-content" / "plugins"
        plugin_paths: List[Path] = []
        for child in plugins_root.iterdir():
            if child.name.startswith("."):
                continue
            if child.is_dir() or (child.is_file() and child.suffix == ".zip"):
                plugin_paths.append(child)
        return plugin_paths


def scan_path(
    path: str | Path,
    strict: bool = True,
    include_suspicious: bool = False,
    wp_version: str = "latest",
    php_version: str = "8.2",
    config_path: Path | None = None,
    vuln_db: Path | None = None,
) -> PluginReport:
    scanner = Scanner(
        strict=strict,
        include_suspicious=include_suspicious,
        wp_version=wp_version,
        php_version=php_version,
        config_path=config_path,
        vuln_db=vuln_db,
    )
    return scanner.scan_path(Path(path))
