#!/usr/bin/env python3
"""Plot dryer CSV logs from capture_csv.py, the TUI, or firmware `log on`.

Example:
  python3 scripts/plot_logs.py logs/dryer_20260707_195354.csv
  python3 scripts/plot_logs.py logs/          # newest .csv in directory
  python3 scripts/plot_logs.py remote         # newest .csv on alex@10.81.16.44
  python3 scripts/plot_logs.py logs/foo.csv -o plot.png
"""

from __future__ import annotations

import argparse
import csv
import subprocess
import sys
import tempfile
from pathlib import Path

REMOTE_SSH = "alex@10.81.16.44"
REMOTE_LOG_DIRS = (
    "~/arduino-filament-dryer/logs",
    "~/voron-filament-dryer/logs",
    "~/logs",
)


def import_matplotlib():
    try:
        import matplotlib.pyplot as plt
    except ImportError:
        print("Install matplotlib: pip install matplotlib", file=sys.stderr)
        raise SystemExit(1) from None
    return plt


def fetch_remote_csv(remote_dirs: tuple[str, ...] = REMOTE_LOG_DIRS) -> Path:
    dir_list = " ".join(remote_dirs)
    find_cmd = (
        f"for d in {dir_list}; do "
        'if [ -d "$d" ]; then ls -t "$d"/*.csv 2>/dev/null; fi; done | head -1'
    )
    result = subprocess.run(
        ["ssh", REMOTE_SSH, find_cmd],
        capture_output=True,
        text=True,
        check=False,
    )
    remote_path = result.stdout.strip()
    if result.returncode != 0 or not remote_path:
        err = result.stderr.strip()
        print(f"No remote CSV found on {REMOTE_SSH}", file=sys.stderr)
        if err:
            print(err, file=sys.stderr)
        raise SystemExit(1)

    local_path = Path(tempfile.gettempdir()) / f"dryer_remote_{Path(remote_path).name}"
    scp = subprocess.run(
        ["scp", f"{REMOTE_SSH}:{remote_path}", str(local_path)],
        capture_output=True,
        text=True,
        check=False,
    )
    if scp.returncode != 0:
        print(f"scp failed for {REMOTE_SSH}:{remote_path}", file=sys.stderr)
        if scp.stderr.strip():
            print(scp.stderr.strip(), file=sys.stderr)
        raise SystemExit(1)

    print(f"Fetched {REMOTE_SSH}:{remote_path}", file=sys.stderr)
    return local_path


def resolve_csv(path: Path) -> Path:
    if path.is_dir():
        matches = sorted(path.glob("*.csv"), key=lambda p: p.stat().st_mtime, reverse=True)
        if not matches:
            print(f"No CSV files in {path}", file=sys.stderr)
            raise SystemExit(1)
        return matches[0]
    if not path.is_file():
        print(f"Not found: {path}", file=sys.stderr)
        raise SystemExit(1)
    return path


def load_rows(path: Path) -> tuple[list[str], list[dict[str, str]]]:
    with path.open(newline="", encoding="utf-8") as fh:
        reader = csv.DictReader(fh)
        if reader.fieldnames is None:
            print(f"Empty CSV: {path}", file=sys.stderr)
            raise SystemExit(1)
        rows = list(reader)
    return list(reader.fieldnames), rows


def column_float(rows: list[dict[str, str]], name: str) -> list[float | None]:
    out: list[float | None] = []
    for row in rows:
        raw = row.get(name, "").strip()
        if not raw:
            out.append(None)
            continue
        try:
            out.append(float(raw))
        except ValueError:
            out.append(None)
    return out


def time_axis(rows: list[dict[str, str]], fieldnames: list[str]) -> tuple[list[float], str]:
    if "ms" in fieldnames:
        ms = column_float(rows, "ms")
        if any(v is not None for v in ms):
            t0 = next(v for v in ms if v is not None)
            return [((v or t0) - t0) / 60000.0 for v in ms], "minutes since start"

    if "wall_time" in fieldnames:
        from datetime import datetime

        times: list[float] = []
        parsed: list[datetime] = []
        for row in rows:
            raw = row.get("wall_time", "").strip()
            if not raw:
                continue
            try:
                parsed.append(datetime.fromisoformat(raw))
            except ValueError:
                continue
        if parsed:
            t0 = parsed[0]
            for dt in parsed:
                times.append((dt - t0).total_seconds() / 60.0)
            return times, "minutes since start"

    return [float(i) for i in range(len(rows))], "sample"


def plot_csv(path: Path, output: Path | None, title: str | None = None) -> None:
    plt = import_matplotlib()

    fieldnames, rows = load_rows(path)
    if not rows:
        print(f"No data rows in {path}", file=sys.stderr)
        raise SystemExit(1)

    x, x_label = time_axis(rows, fieldnames)
    if len(x) != len(rows):
        x = [float(i) for i in range(len(rows))]
        x_label = "sample"

    fig, axes = plt.subplots(3, 1, figsize=(11, 8), sharex=True, constrained_layout=True)
    fig.suptitle(title or path.name)

    temp_ax = axes[0]
    for col, label, style in (
        ("avg_c", "avg", "-"),
        ("min_c", "min", "--"),
        ("max_c", "max", "--"),
        ("target_c", "target", ":"),
    ):
        if col not in fieldnames:
            continue
        y = column_float(rows, col)
        temp_ax.plot(x, y, style, label=label, linewidth=1.5 if col == "avg_c" else 1.0)
    for ch in (2, 3, 4, 5):
        col = f"ch{ch}_t"
        if col in fieldnames:
            y = column_float(rows, col)
            temp_ax.plot(x, y, "-", alpha=0.35, linewidth=0.8, label=f"ch{ch}")
    temp_ax.set_ylabel("°C")
    temp_ax.legend(loc="upper left", ncol=4, fontsize=8)
    temp_ax.grid(True, alpha=0.3)

    duty_ax = axes[1]
    if "heater_pct" in fieldnames:
        duty_ax.plot(x, column_float(rows, "heater_pct"), "C1-", label="heater %")
    if "heatlim_pct" in fieldnames:
        duty_ax.plot(x, column_float(rows, "heatlim_pct"), "C1--", alpha=0.6, label="heatlim %")
    if "fan_pct" in fieldnames:
        duty_ax.plot(x, column_float(rows, "fan_pct"), "C0-", label="fan %")
    duty_ax.set_ylabel("%")
    duty_ax.legend(loc="upper left", fontsize=8)
    duty_ax.grid(True, alpha=0.3)

    spread_ax = axes[2]
    if "spread_c" in fieldnames:
        spread_ax.plot(x, column_float(rows, "spread_c"), "C2-", label="spread")
    spread_ax.set_ylabel("°C")
    spread_ax.set_xlabel(x_label)
    spread_ax.legend(loc="upper left", fontsize=8)
    spread_ax.grid(True, alpha=0.3)

    if output is not None:
        fig.savefig(output, dpi=150)
        print(f"Wrote {output}")
    else:
        plt.show()


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Plot dryer CSV temperature logs")
    parser.add_argument(
        "csv",
        help='CSV file, directory (newest .csv), or "remote" for newest on ' + REMOTE_SSH,
    )
    parser.add_argument("-o", "--output", type=Path, help="Save PNG instead of opening a window")
    parser.add_argument(
        "--remote-dir",
        action="append",
        metavar="DIR",
        help=f"Remote log directory on {REMOTE_SSH} (repeatable; used with remote)",
    )
    args = parser.parse_args(argv)

    plot_title: str | None = None
    if args.csv == "remote":
        remote_dirs = tuple(args.remote_dir) if args.remote_dir else REMOTE_LOG_DIRS
        path = fetch_remote_csv(remote_dirs)
        plot_title = f"{REMOTE_SSH}:{path.name}"
    else:
        path = resolve_csv(Path(args.csv))

    if args.output is None:
        print(f"Plotting {plot_title or path}", file=sys.stderr)
    plot_csv(path, args.output, title=plot_title)
    return 0


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