from __future__ import annotations

import platform
import socket
import time
from pathlib import Path
from typing import Any, Dict, List, Optional

from auditwalk.core.timeutil import local_tz_offset, make_run_id, now_iso
from auditwalk.detectors.filesystem import run_filesystem_detectors
from auditwalk.detectors.processes import run_process_detectors
from auditwalk.runs.run_store import RunStore
from auditwalk.scan.collectors import disk, files, firewall, installed, network, persistence, processes, system


def default_roots(mode: str) -> list[str]:
    sysname = platform.system().lower()
    home = str(Path.home())
    if "windows" in sysname:
        return [home] if mode == "quick" else ["C:\\", home]
    if "darwin" in sysname or "mac" in sysname:
        return [home] if mode == "quick" else [home, "/etc", "/Applications"]
    return [home] if mode == "quick" else [home, "/etc"]


def _collect_run_signals(
    *,
    roots: list[str],
    max_files: int,
    timeout_seconds: int,
    candidate_paths: list[Path] | None = None,
    process_items: list[dict] | None = None,
) -> list[tuple[str, float, dict]]:
    """Collect raw detector signals for scanner-created runs.

    This is intentionally detector-only (no scoring) so scanner can persist signals.json
    without depending on transfer_assessment orchestration.
    """
    started_at = time.time()
    signals: list[tuple[str, float, dict]] = []

    def add_signal(code: str, confidence: float, evidence: dict) -> None:
        if not isinstance(code, str) or not code or not isinstance(evidence, dict):
            return
        try:
            conf_f = float(confidence)
        except (TypeError, ValueError):
            return
        signals.append((code, conf_f, evidence))

    # Scanner path currently lacks baseline/threat-policy context; run detector in minimal mode
    # to persist raw heuristics (e.g. header mismatch) for later compare composition.
    if process_items:
        run_process_detectors(processes=process_items, add_signal=add_signal)

    # If scanner already collected file paths, reuse them to avoid a second filesystem walk.
    if candidate_paths is not None:
        run_filesystem_detectors(
            scan_path=Path(roots[0]).expanduser().resolve() if roots else Path(".").resolve(),
            started_at=started_at,
            max_scan_seconds=timeout_seconds,
            max_files=max_files,
            recent_cutoff=started_at,
            suspicious_extensions=set(),
            wallet_tokens=set(),
            baseline={},
            known_bad_hashes=set(),
            add_signal=add_signal,
            candidate_paths=candidate_paths,
        )
        return signals

    for root in roots:
        root_path = Path(root).expanduser().resolve()
        run_filesystem_detectors(
            scan_path=root_path,
            started_at=started_at,
            max_scan_seconds=timeout_seconds,
            max_files=max_files,
            recent_cutoff=started_at,
            suspicious_extensions=set(),
            wallet_tokens=set(),
            baseline={},
            known_bad_hashes=set(),
            add_signal=add_signal,
        )

    return signals


def scan(
    *,
    mode: str,
    roots: Optional[List[str]] = None,
    exclude: Optional[List[str]] = None,
    label: Optional[str] = None,
    baseline: bool = False,
    do_hash: bool = False,
    max_files: int = 200000,
    max_procs: int = 5000,
    timeout_seconds: int = 300,
    strict_files: bool = False,
    data_dir: str | None = None,
) -> Dict[str, Any]:
    """MVP scanner orchestrator writing canonical run artifacts."""
    run_id = make_run_id()
    created = now_iso()
    created_utc = int(time.time())
    root_list = roots or default_roots(mode)
    exclude_list = exclude or []

    system_name = platform.system().lower()
    if "windows" in system_name:
        os_family = "windows"
    elif "darwin" in system_name or "mac" in system_name:
        os_family = "macos"
    elif "linux" in system_name:
        os_family = "linux"
    else:
        os_family = "unknown"

    system_result = system.collect()
    disk_result = disk.collect(root_list)
    proc_result = processes.collect(max_procs=max_procs)
    net_result = network.collect()
    persist_result = persistence.collect()
    installed_result = installed.collect()
    files_result = files.collect(
        root_list,
        exclude=exclude_list,
        do_hash=do_hash,
        max_files=max_files,
        timeout_seconds=timeout_seconds,
        strict_files=strict_files,
    )
    firewall_result = firewall.collect()

    collector_status = {
        "system": system_result.get("status", "error"),
        "disk": disk_result.get("status", "error"),
        "processes": proc_result.get("status", "error"),
        "network": net_result.get("status", "error"),
        "persistence": persist_result.get("status", "error"),
        "installed": installed_result.get("status", "error"),
        "files": files_result.get("status", "error"),
        "firewall": firewall_result.get("status", "error"),
    }

    warnings = (
        list(system_result.get("warnings", []))
        + list(disk_result.get("warnings", []))
        + list(proc_result.get("warnings", []))
        + list(net_result.get("warnings", []))
        + list(persist_result.get("warnings", []))
        + list(installed_result.get("warnings", []))
        + list(files_result.get("warnings", []))
        + list(firewall_result.get("warnings", []))
    )
    errors = (
        list(system_result.get("errors", []))
        + list(disk_result.get("errors", []))
        + list(proc_result.get("errors", []))
        + list(net_result.get("errors", []))
        + list(persist_result.get("errors", []))
        + list(installed_result.get("errors", []))
        + list(files_result.get("errors", []))
        + list(firewall_result.get("errors", []))
    )

    system_data = dict(system_result.get("data", {}) if isinstance(system_result.get("data"), dict) else {})

    artifacts = {
        "meta": {
            "schema_version": 1,
            "product": "auditwalk",
            "product_version": "0.1.0",
            "run_id": run_id,
            "created_utc": created_utc,
            "created_tz_offset": local_tz_offset(),
            "mode": mode,
            "label": label,
            "baseline": baseline,
            "host": {
                "hostname": socket.gethostname(),
                "os_family": os_family,
                "os_name": platform.system() or "Unknown",
                "os_version": platform.release() or "Unknown",
                "arch": platform.machine() or "unknown",
            },
            "collector_status": collector_status,
            "options": {
                "roots": root_list,
                "exclude": exclude_list,
                "hash": do_hash,
                "max_files": max_files,
                "max_procs": max_procs,
                "timeout_seconds": timeout_seconds,
                "strict_files": strict_files,
            },
            "collector_summaries": {
                "files": files_result.get("data", {}).get("collector_summary", {}),
                "files_partial_reason": files_result.get("data", {}).get("partial_reason"),
            },
            "notes": {
                "warnings": warnings,
                "errors": errors,
            },
        },
        "system": {
            **system_data,
            "created_at": created,
            "disk": disk_result.get("data", {}),
            "installed": installed_result.get("data", {}),
            "firewall": firewall_result.get("data", {}),
            "notes": {
                "warnings": warnings,
                "errors": errors,
            },
        },
        "files": files_result.get("data", {"items": [], "truncated": False}),
        "processes": proc_result.get("data", {"items": [], "truncated": False}),
        "network": net_result.get("data", {"interfaces": [], "connections": []}),
        "persistence": persist_result.get("data", {"startup_items": []}),
        "score": {
            "total": 0,
            "tier": "Clean",
            "breakdown": {
                "filesystem": 0,
                "persistence": 0,
                "network": 0,
                "processes": 0,
                "configuration": 0,
            },
            "findings": [],
        },
    }

    store = RunStore(data_dir)
    run_path = store.create_run(run_id, artifacts, mode=mode, label=label)
    candidate_paths: list[Path] = []
    for item in artifacts["files"].get("items", []):
        if not isinstance(item, dict):
            continue
        p = item.get("path")
        if isinstance(p, str) and p:
            candidate_paths.append(Path(p))
    process_items = artifacts["processes"].get("items", []) if isinstance(artifacts["processes"], dict) else []
    store.save_run_signals(
        run_id,
        _collect_run_signals(
            roots=root_list,
            max_files=max_files,
            timeout_seconds=timeout_seconds,
            candidate_paths=candidate_paths,
            process_items=process_items if isinstance(process_items, list) else None,
        ),
    )

    return {
        "run_id": run_id,
        "run_path": str(run_path),
        "mode": mode,
        "label": label,
        "score": artifacts["score"],
        "top_findings": artifacts["score"]["findings"][:3],
    }
