"""Per-frame firmware image storage + esp_app_desc_t parsing. Shared by
the manual upload path and the Gitea auto-update path -- both end up
writing the same per-frame slot that GET /frame/firmware streams to the
device. The migration moves the old single /data/firmware.bin into frame
#1's slot."""

from __future__ import annotations

from fastapi import HTTPException

from . import config

# ESP-IDF app images embed an esp_app_desc_t at byte offset 32 (24-byte
# image header + 8-byte first-segment header): magic word, then version
# (32 bytes, NUL-padded) at +16 and project name (32 bytes) at +48 --
# verified against this project's real build artifact.
APP_DESC_OFFSET = 32
APP_DESC_MAGIC = 0xABCD5432
EXPECTED_PROJECT_NAME = "espresso_frame"


def firmware_path(frame_id: int):
    return config.CONFIG_PATH.parent / "firmware" / f"{frame_id}.bin"


def parse_app_version(data: bytes) -> str:
    """Extracts the embedded version from an ESP-IDF app image, raising
    HTTPException(400) for anything that isn't this project's firmware."""
    if len(data) < APP_DESC_OFFSET + 80:
        raise HTTPException(400, "File is too small to be a firmware image")
    magic = int.from_bytes(data[APP_DESC_OFFSET : APP_DESC_OFFSET + 4], "little")
    if magic != APP_DESC_MAGIC:
        raise HTTPException(400, "Not an ESP-IDF application image")
    version = data[APP_DESC_OFFSET + 16 : APP_DESC_OFFSET + 48].split(b"\x00")[0].decode(errors="replace")
    project = data[APP_DESC_OFFSET + 48 : APP_DESC_OFFSET + 80].split(b"\x00")[0].decode(errors="replace")
    if project != EXPECTED_PROJECT_NAME:
        raise HTTPException(400, f"Image is for project '{project}', not '{EXPECTED_PROJECT_NAME}'")
    if not version:
        raise HTTPException(400, "Image has no embedded version")
    return version
