r"""Inspect ATM strip pixels without relying on shell pipelines.

Usage:
  .\.venv\Scripts\python.exe scripts\inspect_image_pixels.py 6033117943853423831.jpg
  .\.venv\Scripts\python.exe scripts\inspect_image_pixels.py image.jpg --point 1780 725

The script intentionally parses only the config fields needed for pixel inspection,
so it does not require Discord/Telegram secrets to be valid.
"""
from __future__ import annotations

import argparse
import json
import sys
import tomllib
from pathlib import Path

import cv2

ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src"
if str(SRC) not in sys.path:
    sys.path.insert(0, str(SRC))

from atm.config import ROI  # noqa: E402
from atm.vision import classify_pixel, crop_roi, find_top_dots, pixel_rgb  # noqa: E402


def _load_probe_config(path: Path) -> dict:
    data = tomllib.loads(path.read_text(encoding="utf-8"))
    colors = {
        name: (tuple(int(c) for c in spec["rgb"]), float(spec["tolerance"]))
        for name, spec in data["colors"].items()
    }
    background = colors.pop("background", ((18, 18, 18), 15.0))
    return {
        "dot_roi": ROI(**data["dot_roi"]),
        "colors": colors,
        "background_rgb": background[0],
        "background_tol": background[1],
    }


def _as_jsonable_match(match) -> dict:
    return {
        "name": match.name,
        "distance": round(float(match.distance), 3),
        "confidence": round(float(match.confidence), 3),
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Inspect ATM strip pixels in a JPG/PNG frame.")
    parser.add_argument("image", type=Path, help="Frame image path.")
    parser.add_argument(
        "--config",
        type=Path,
        default=ROOT / "configs" / "2026-04-21-recalib.toml",
        help="ATM TOML config path.",
    )
    parser.add_argument("--top", type=int, default=3, help="Number of rightmost dots to report.")
    parser.add_argument(
        "--point",
        nargs=2,
        type=int,
        metavar=("X", "Y"),
        help="Optional absolute pixel coordinate to sample.",
    )
    parser.add_argument("--box", type=int, default=3, help="Sampling radius for mean RGB.")
    args = parser.parse_args()

    frame = cv2.imread(str(args.image), cv2.IMREAD_COLOR)
    if frame is None:
        raise SystemExit(f"Could not read image: {args.image}")

    probe = _load_probe_config(args.config)
    roi = probe["dot_roi"]
    roi_img = crop_roi(frame, roi)
    dots = find_top_dots(
        roi_img,
        bg_rgb=probe["background_rgb"],
        bg_tol=probe["background_tol"],
        n=args.top,
    )

    result = {
        "image": str(args.image),
        "image_size": {"w": int(frame.shape[1]), "h": int(frame.shape[0])},
        "config": str(args.config),
        "dot_roi": {"x": roi.x, "y": roi.y, "w": roi.w, "h": roi.h},
        "dots": [],
    }

    for x, y in dots:
        rgb = pixel_rgb(roi_img, x, y, box=args.box)
        match = classify_pixel(rgb, probe["colors"])
        result["dots"].append(
            {
                "roi_xy": [int(x), int(y)],
                "abs_xy": [int(roi.x + x), int(roi.y + y)],
                "rgb": list(rgb),
                "match": _as_jsonable_match(match),
            }
        )

    if args.point:
        px, py = args.point
        if not (roi.x <= px < roi.x + roi.w and roi.y <= py < roi.y + roi.h):
            raise SystemExit(f"Point {px},{py} is outside dot_roi")
        rx, ry = px - roi.x, py - roi.y
        rgb = pixel_rgb(roi_img, rx, ry, box=args.box)
        result["point"] = {
            "roi_xy": [rx, ry],
            "abs_xy": [px, py],
            "rgb": list(rgb),
            "match": _as_jsonable_match(classify_pixel(rgb, probe["colors"])),
        }

    print(json.dumps(result, indent=2))
    return 0


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