#!/usr/bin/env python3
"""Fase 0 — descoberta leve de hosts com portas típicas de câmeras IP.

Não autentica e não assume fabricante. Apenas TCP connect em portas comuns.
"""

from __future__ import annotations

import argparse
import concurrent.futures
import ipaddress
import json
import socket
import sys
from datetime import datetime, timezone
from pathlib import Path

DEFAULT_PORTS = (80, 554, 8000, 8080, 8899)
ROOT = Path(__file__).resolve().parents[2]
OUT_DIR = ROOT / "results" / "cameras"


def probe(ip: str, port: int, timeout: float) -> bool:
    try:
        with socket.create_connection((ip, port), timeout=timeout):
            return True
    except OSError:
        return False


def scan_host(ip: str, ports: tuple[int, ...], timeout: float) -> dict | None:
    open_ports = [p for p in ports if probe(ip, p, timeout)]
    if not open_ports:
        return None
    return {
        "ip": ip,
        "open_ports": open_ports,
        "hints": {
            "possible_rtsp": 554 in open_ports,
            "possible_http_onvif": any(p in open_ports for p in (80, 8000, 8080, 8899)),
        },
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Descoberta leve de hosts (portas câmera/ONVIF/RTSP)")
    parser.add_argument("--cidr", required=True, help="Ex.: 10.10.15.0/24")
    parser.add_argument("--timeout", type=float, default=0.4)
    parser.add_argument("--workers", type=int, default=128)
    parser.add_argument(
        "--ports",
        default=",".join(str(p) for p in DEFAULT_PORTS),
        help="Lista de portas separadas por vírgula",
    )
    args = parser.parse_args()

    ports = tuple(int(p.strip()) for p in args.ports.split(",") if p.strip())
    network = ipaddress.ip_network(args.cidr, strict=False)
    hosts = [str(h) for h in network.hosts()]

    print(f"Varrendo {len(hosts)} hosts em {args.cidr} portas {list(ports)} ...", file=sys.stderr)

    found: list[dict] = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
        futs = {pool.submit(scan_host, ip, ports, args.timeout): ip for ip in hosts}
        for fut in concurrent.futures.as_completed(futs):
            item = fut.result()
            if item:
                found.append(item)
                print(f"  + {item['ip']} → {item['open_ports']}", file=sys.stderr)

    found.sort(key=lambda x: tuple(int(p) for p in x["ip"].split(".")))
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    payload = {
        "collected_at_utc": stamp,
        "cidr": args.cidr,
        "ports": list(ports),
        "hosts_found": len(found),
        "hosts": found,
        "notes": [
            "Descoberta passiva de portas apenas — sem autenticação.",
            "Preencha tools/camera-inventory/cameras.yaml com IPs candidatos e credenciais.",
            "Não assume fabricante.",
        ],
    }
    out_json = OUT_DIR / f"discover-{stamp}.json"
    latest = OUT_DIR / "discover-latest.json"
    out_json.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    latest.write_text(out_json.read_text(encoding="utf-8"), encoding="utf-8")
    print(json.dumps({"hosts_found": len(found), "output": str(out_json)}, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
