"""Pydantic data models for BCM4775 firmware operations.

Models for the binary firmware update protocol used by the Broadcom
BCM4775 GNSS SoC inside Quectel LC29H modules. Separate from the
NMEA-level models in models.py.
"""

from enum import IntEnum

from pydantic import BaseModel, Field


class FirmwareDownloaderState(IntEnum):
    """18-state FSM from FlashUpdater4775 firmware_downloader.cpp.

    Decompiled from FUN_00517b80, state variable at this+0x4B0.
    """

    FD_IDLE = 0x00
    FD_REQUEST_DOWNLOAD = 0x01
    FD_WAIT4AUTOBAUD = 0x02
    FD_SENT_AUTOBAUD = 0x03
    FD_WAIT4SYNC = 0x04
    FD_GOT_SYNC = 0x05
    FD_TIMEOUT_SYNC = 0x06
    FD_GOT_SYNC_INIT = 0x07
    FD_WAIT4VERSION_INIT = 0x08
    FD_GOT_VERSION_INIT = 0x09
    FD_ROMRESET = 0x0A
    FD_WAIT4DOWNLOAD_START = 0x0B
    FD_DOWNLOADING = 0x0C
    FD_WAIT4APP = 0x0D
    FD_GOT_SYNC_CONFIRM = 0x0E
    FD_WAIT4VERSION_CONFIRM = 0x0F
    FD_GOT_VERSION_CONFIRM = 0x10
    FD_DONE = 0x11


class RpcCommandId(IntEnum):
    """RPC command IDs registered in OnLhReady callback.

    These are registered after the bootloader patch is loaded and running.
    """

    FLASH_READ = 6
    FLASH_WRITE = 9
    FLASH_ERASE = 10
    CONFIG_QUERY = 35
    CRC_VERIFY = 36
    STATUS = 39


# BCM477x chip ID validation table from firmware_downloader.cpp state 9.
# Maps chip_id → human-readable name. The firmware updater rejects any
# chip ID not in this table.
BCM_CHIP_IDS: dict[int, str] = {
    0x47730A30: "BCM4773 rev A30",
    0x47730A20: "BCM4773 rev A20",
    0x004774A0: "BCM4774 rev A0",
    0x004775A0: "BCM4775 rev A0",
    0x004775A1: "BCM4775 rev A1",
    0x004775B0: "BCM4775 rev B0",
    0x034775B0: "BCM4775 rev B0 (variant 3)",
    0x074775B0: "BCM4775 rev B0 (variant 7)",
    0x054775B0: "BCM4775 rev B0 (variant 5)",
    0x044775B0: "BCM4775 rev B0 (variant 4)",
    0x0D4775B1: "BCM4775 rev B1 (variant D)",
    0x044775B1: "BCM4775 rev B1 (variant 4)",
    0x004775B1: "BCM4775 rev B1",
}


class FlashConfig(BaseModel):
    """Response from RPC CONFIG_QUERY (ID=35)."""

    flash_size: int = Field(description="Flash device size in bytes")
    sector_erase_time_ms: int = Field(description="Time to erase one 4KB sector in milliseconds")


class CrcResult(BaseModel):
    """Response from RPC CRC_VERIFY (ID=36)."""

    address: int = Field(description="Flash address that was verified")
    size: int = Field(description="Number of bytes verified")
    crc: int = Field(description="CRC-8 of the flash region (0x00-0xFF)")
    match: bool = Field(description="Whether CRC matches expected value")


class FlashStatus(BaseModel):
    """Response from RPC STATUS (ID=39)."""

    code: int = Field(description="Status code from BCM4775")
    busy: bool = Field(description="Whether a flash operation is in progress")


class FirmwareInfo(BaseModel):
    """Combined NMEA + bootloader info about the module."""

    model: str = Field(default="", description="Module model (e.g. LC29HDA)")
    firmware_version: str = Field(default="", description="NMEA firmware version string")
    chip_id: int = Field(default=0, description="BCM477x chip ID from bootloader")
    chip_name: str = Field(default="", description="Human-readable chip name from ID table")
    bootloader_active: bool = Field(default=False, description="Whether bootloader is running")


class BackupResult(BaseModel):
    """Result of a flash backup operation."""

    path: str = Field(description="Path to the backup file")
    size: int = Field(description="Number of bytes backed up")
    crc: int = Field(description="CRC of the backup data")


class UpdateResult(BaseModel):
    """Result of a firmware update operation."""

    success: bool = Field(description="Whether the update completed successfully")
    chip_id: int = Field(default=0, description="Validated chip ID")
    chip_name: str = Field(default="", description="Human-readable chip name")
    bytes_written: int = Field(default=0, description="Total bytes written to flash")
    verified: bool = Field(default=False, description="Whether CRC verification passed")
    backup_path: str = Field(default="", description="Path to pre-update backup (empty if skipped)")
    error: str = Field(default="", description="Error message if update failed")
