#!/usr/bin/env python3
"""Capture ESP32 serial logs for hardware troubleshooting."""

from __future__ import annotations

import argparse
import sys
import time
from pathlib import Path

from serial import Serial
from serial.tools import list_ports


DEFAULT_PORT_GLOB = "wchusbserial"


def find_default_port() -> str | None:
    for port in list_ports.comports():
        haystack = " ".join(
            str(value)
            for value in (port.device, port.description, port.manufacturer, port.hwid)
            if value
        ).lower()
        if DEFAULT_PORT_GLOB in haystack or "ch340" in haystack:
            return port.device
    return None


def list_serial_ports() -> int:
    ports = list(list_ports.comports())
    if not ports:
        print("No serial ports found.")
        return 1

    for port in ports:
        details = []
        if port.description:
            details.append(port.description)
        if port.manufacturer:
            details.append(port.manufacturer)
        suffix = f" ({'; '.join(details)})" if details else ""
        print(f"{port.device}{suffix}")
    return 0


def capture(args: argparse.Namespace) -> int:
    port = args.port or find_default_port()
    if not port:
        print("No CH340/WCH serial port found. Try --list, then pass --port /dev/cu....", file=sys.stderr)
        return 2

    out_file = Path(args.out).expanduser() if args.out else None
    output = out_file.open("w", encoding="utf-8") if out_file else None
    saw_usage = False
    saw_connected = False
    saw_error = False

    print(f"Opening {port} at {args.baud} baud for {args.seconds}s...")
    print("Tip: press the ESP32 reset button now if you want a boot log.")

    deadline = time.monotonic() + args.seconds
    try:
        with Serial(port, args.baud, timeout=0.25) as ser:
            if args.reset:
                # Toggle RTS/DTR to reset common ESP32 dev boards without a
                # physical button press. Some CH340 boards invert these lines,
                # so use the esptool-style pulse and then clear both.
                ser.dtr = False
                ser.rts = True
                time.sleep(0.1)
                ser.dtr = True
                ser.rts = False
                time.sleep(0.1)
                ser.dtr = False
                ser.rts = False
                time.sleep(0.5)
                ser.reset_input_buffer()
            while time.monotonic() < deadline:
                raw = ser.readline()
                if not raw:
                    continue
                line = raw.decode("utf-8", errors="replace").rstrip("\r\n")
                print(line)
                if output:
                    output.write(line + "\n")
                if "[HTTP] Parsed" in line and "providers" in line:
                    saw_usage = True
                if "[WiFi] Connected" in line:
                    saw_connected = True
                if "[HTTP] Error" in line or "[WiFi] Reconnect failed" in line:
                    saw_error = True
    except KeyboardInterrupt:
        print("\nStopped.")
    except Exception as exc:
        print(f"Could not read {port}: {exc}", file=sys.stderr)
        return 2
    finally:
        if output:
            output.close()

    if out_file:
        print(f"Saved log to {out_file}")

    print()
    print("Summary:")
    print(f"  WiFi connected: {'yes' if saw_connected else 'no'}")
    print(f"  Usage parsed:   {'yes' if saw_usage else 'no'}")
    print(f"  Errors seen:    {'yes' if saw_error else 'no'}")

    if args.expect_usage and not saw_usage:
        return 1
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--list", action="store_true", help="list serial ports and exit")
    parser.add_argument("--port", help="serial port, for example /dev/cu.wchusbserial110")
    parser.add_argument("--baud", type=int, default=115200)
    parser.add_argument("--seconds", type=int, default=60)
    parser.add_argument("--out", help="optional file to save captured serial output")
    parser.add_argument("--expect-usage", action="store_true", help="exit non-zero unless usage data is parsed")
    parser.add_argument("--reset", action="store_true", help="toggle DTR/RTS after opening to capture boot logs")
    args = parser.parse_args()

    if args.list:
        return list_serial_ports()
    return capture(args)


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