#!/usr/bin/env python3
"""Collect SealMiner `stats` replies from a list of miners into JSON Lines.

Uses the documented SealMiner API (Sealminer API V0.5): TCP port 4028, JSON command
{"command": "stats"}. Read-only: it never sends ascset, restart or reboot.

Usage:
  python3 sealminer_collect.py ips.txt > fleet.jsonl        # one IP per line, '#' comments allowed
  python3 sealminer_collect.py 10.0.0.11 10.0.0.12 > fleet.jsonl

Each output line: {"ip": ..., "collected_at": ..., "ok": true, "reply": {...}} or
{"ip": ..., "ok": false, "error": "..."}. Upload fleet.jsonl to the Fleet scan on the
Field Quality Desk (Unit check tab). Standard library only; Python 3.8+.
"""
import json
import socket
import sys
import time
from concurrent.futures import ThreadPoolExecutor

PORT = 4028
TIMEOUT = 6.0


def query(ip: str, command: str = "stats") -> dict:
    with socket.create_connection((ip, PORT), timeout=TIMEOUT) as s:
        s.sendall(json.dumps({"command": command}).encode())
        chunks = []
        while True:
            try:
                data = s.recv(65536)
            except socket.timeout:
                break
            if not data:
                break
            chunks.append(data)
    raw = b"".join(chunks).replace(b"\x00", b"").decode("utf-8", "replace").strip()
    # Some cgminer-family firmwares emit "}{" between objects or trailing commas; tidy before parsing.
    raw = raw.replace("}{", "},{")
    return json.loads(raw)


def collect(ip: str) -> dict:
    row = {"ip": ip, "collected_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}
    try:
        row["reply"] = query(ip)
        row["ok"] = True
    except Exception as e:  # report and keep going: one dead miner must not stop the sweep
        row["ok"] = False
        row["error"] = f"{type(e).__name__}: {e}"
    return row


def main(argv):
    if not argv:
        print(__doc__, file=sys.stderr)
        return 2
    ips = []
    for a in argv:
        try:
            with open(a) as f:
                ips += [l.split("#")[0].strip() for l in f if l.split("#")[0].strip()]
        except FileNotFoundError:
            ips.append(a)
    with ThreadPoolExecutor(max_workers=32) as pool:
        for row in pool.map(collect, ips):
            print(json.dumps(row, separators=(",", ":")))
            sys.stdout.flush()
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
