#!/usr/bin/env python3
"""Capture a camera frame and send one Farsight observation. Requires FFmpeg."""

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen

API_BASE = os.environ.get("FARSIGHT_BASE_URL", "https://farsight.observer").rstrip("/")
KEY_FILE = Path(os.environ.get("FARSIGHT_KEY_FILE", "~/.config/farsight/api_key")).expanduser()


def camera_input():
    if sys.platform == "darwin":
        cameras = {
            "top": ["-f", "avfoundation", "-i", "0:none"],
            # "side": ["-f", "avfoundation", "-i", "1:none"],
            # "front": ["-f", "avfoundation", "-i", "2:none"],
        }
    elif sys.platform.startswith("linux"):
        cameras = {
            "top": ["-f", "v4l2", "-i", "/dev/video0"],
            # "side": ["-f", "v4l2", "-i", "/dev/video2"],
            # "front": ["-f", "v4l2", "-i", "/dev/video4"],
        }
    else:
        raise RuntimeError("Camera capture supports Linux and macOS")
    return cameras


def api_key():
    key = os.environ.get("FARSIGHT_API_KEY", "").strip()
    if not key and KEY_FILE.is_file():
        key = KEY_FILE.read_text(encoding="utf-8").strip()
    if not key:
        raise RuntimeError(f"Set FARSIGHT_API_KEY or save the key in {KEY_FILE}")
    return key


def send(chamber_id, view, frame, key):
    url = (f"{API_BASE}/v1/chambers/{quote(chamber_id, safe='')}/observations?"
           f"{urlencode({'view': view})}")
    request = Request(url, data=frame, method="POST", headers={
        "Authorization": f"Bearer {key}", "Content-Type": "image/jpeg",
    })
    with urlopen(request, timeout=30) as response:
        if response.status != 202:
            raise RuntimeError(f"Expected HTTP 202, got {response.status}")
        result = json.load(response)
    if not result.get("id"):
        raise RuntimeError("Farsight returned no observation id")
    return result


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--chamber", default=os.environ.get("FARSIGHT_CHAMBER_ID"), required=False,
                        help="Chamber ID (or set FARSIGHT_CHAMBER_ID)")
    parser.add_argument("--view", default="top", help="Configured camera view name")
    parser.add_argument("--all", action="store_true", help="Send one frame from each configured camera")
    parser.add_argument("--image", type=Path, help="Use an existing JPEG instead of a camera")
    args = parser.parse_args()
    if not args.chamber:
        parser.error("set --chamber or FARSIGHT_CHAMBER_ID")
    key = api_key()
    cameras = camera_input() if not args.image else {}
    views = cameras if args.all else [args.view]
    if args.image and args.all:
        parser.error("--all cannot be used with --image")
    for view in views:
        if args.image:
            frame = args.image.read_bytes()
        else:
            if view not in cameras:
                parser.error(f"unknown view {view!r}; configure it in camera_input()")
            frame = subprocess.check_output(
                ["ffmpeg", "-hide_banner", "-loglevel", "error", *cameras[view],
                 "-frames:v", "1", "-c:v", "mjpeg", "-f", "image2pipe", "-"],
                timeout=20,
            )
        if not frame:
            raise RuntimeError(f"Camera {view} returned an empty frame")
        result = send(args.chamber, view, frame, key)
        print(json.dumps({"view": view, "id": result["id"], "status": result.get("status")}))


if __name__ == "__main__":
    try:
        main()
    except (HTTPError, URLError, OSError, subprocess.CalledProcessError, RuntimeError) as error:
        if isinstance(error, HTTPError):
            detail = error.read(1024).decode("utf-8", "replace")
            print(f"Farsight HTTP {error.code}: {detail}", file=sys.stderr)
        else:
            print(f"Error: {error}", file=sys.stderr)
        sys.exit(1)
