"""Formatting helpers for ImageLeaderData and nested metadata structures.

Provides `format_image_leader(leader)` which returns a human-readable dict
and `pretty_print_image_leader(leader)` which returns a multiline string.
"""
from typing import Any, Dict, Optional
import math


def _safe_get(obj: Any, attr: str, default: Any = None) -> Any:
    try:
        return getattr(obj, attr)
    except Exception:
        return default


def _bytes_to_hex(b: Any) -> str:
    try:
        # supports ctypes arrays of c_ubyte or bytes
        return ''.join(f"{int(x):02X}" for x in b)
    except Exception:
        try:
            return bytes(b).hex().upper()
        except Exception:
            return str(b)


def _rad_to_deg(rad: Optional[float]) -> Optional[float]:
    if rad is None:
        return None
    try:
        return float(rad) * (180.0 / math.pi)
    except Exception:
        return None


def format_image_leader(leader: Any) -> Dict[str, Any]:
    """Return a dict with human-friendly fields extracted from `leader`.

    Accepts either a ctypes ImageLeaderData-like object or a dict-like structure.
    """
    out: Dict[str, Any] = {}
    if leader is None:
        return out

    # HEADER_TAG.ID (two bytes)
    header_tag = _safe_get(leader, 'HEADER_TAG', None)
    if header_tag is not None:
        out['HEADER_TAG_ID'] = _bytes_to_hex(_safe_get(header_tag, 'ID', b''))
        out['HEADER_TAG_VALID'] = int(_safe_get(header_tag, 'VALID', 0))
        out['HEADER_TAG_SIZE'] = int(_safe_get(header_tag, 'SIZE', 0))

    # HEADER_DATA (main header)
    hd = _safe_get(leader, 'HEADER_DATA', None)
    if hd is not None:
        out['TYPE'] = int(_safe_get(hd, 'TYPE', 0))
        out['SUBTYPE'] = int(_safe_get(hd, 'SUBTYPE', 0))
        out['FCOUNTER'] = int(_safe_get(hd, 'FCOUNTER', 0))
        out['TIMETAG'] = int(_safe_get(hd, 'TIMETAG', 0))
        out['DX'] = int(_safe_get(hd, 'DX', 0))
        out['DY'] = int(_safe_get(hd, 'DY', 0))
        out['STRIDE'] = int(_safe_get(hd, 'STRIDE', 0))
        out['BPP'] = int(_safe_get(hd, 'BPP', 0))
        out['PALTYPE'] = int(_safe_get(hd, 'PALTYPE', 0))
        # PRODINFO often is an array of two unsigned longs; present hex and ints
        prodinfo = _safe_get(hd, 'PRODINFO', None)
        if prodinfo is not None:
            try:
                out['PRODINFO'] = [int(x) for x in prodinfo]
            except Exception:
                out['PRODINFO'] = str(prodinfo)
        # TOD may be two ushorts
        tod = _safe_get(hd, 'TOD', None)
        if tod is not None:
            try:
                out['TOD'] = [int(x) for x in tod]
            except Exception:
                out['TOD'] = str(tod)

    # GEO_TAG / GEO_DATA
    geo_tag = _safe_get(leader, 'GEO_TAG', None)
    if geo_tag is not None:
        out['GEO_TAG_ID'] = _bytes_to_hex(_safe_get(geo_tag, 'ID', b''))
        out['GEO_TAG_VALID'] = int(_safe_get(geo_tag, 'VALID', 0))
        out['GEO_TAG_SIZE'] = int(_safe_get(geo_tag, 'SIZE', 0))
    geo = _safe_get(leader, 'GEO_DATA', None)
    if geo is not None:
        out['ORIENTATION_rad'] = float(_safe_get(geo, 'ORIENTATION', 0.0))
        out['ORIENTATION_deg'] = _rad_to_deg(_safe_get(geo, 'ORIENTATION', 0.0))
        out['LATITUDE_rad'] = float(_safe_get(geo, 'LATITUDE', 0.0))
        out['LATITUDE_deg'] = _rad_to_deg(_safe_get(geo, 'LATITUDE', 0.0))
        out['LONGITUDE_rad'] = float(_safe_get(geo, 'LONGITUDE', 0.0))
        out['LONGITUDE_deg'] = _rad_to_deg(_safe_get(geo, 'LONGITUDE', 0.0))
        out['REF_X'] = int(_safe_get(geo, 'REF_X', 0))
        out['REF_Y'] = int(_safe_get(geo, 'REF_Y', 0))
        out['SCALE_X'] = float(_safe_get(geo, 'SCALE_X', 0.0))
        out['SCALE_Y'] = float(_safe_get(geo, 'SCALE_Y', 0.0))

    # PIXEL_TAG
    pixel_tag = _safe_get(leader, 'PIXEL_TAG', None)
    if pixel_tag is not None:
        out['PIXEL_TAG_ID'] = _bytes_to_hex(_safe_get(pixel_tag, 'ID', b''))
        out['PIXEL_TAG_VALID'] = int(_safe_get(pixel_tag, 'VALID', 0))
        out['PIXEL_TAG_SIZE'] = int(_safe_get(pixel_tag, 'SIZE', 0))

    return out


def pretty_print_image_leader(leader: Any) -> str:
    d = format_image_leader(leader)
    lines = []
    if not d:
        return "<no leader>"
    lines.append(f"HEADER_TAG.ID: {d.get('HEADER_TAG_ID')}")
    lines.append(f"HEADER_TAG.VALID: {d.get('HEADER_TAG_VALID')}")
    lines.append(f"HEADER_TAG.SIZE: {d.get('HEADER_TAG_SIZE')}")
    lines.append("")
    lines.append(f"TYPE: {d.get('TYPE')}")
    lines.append(f"SUBTYPE: {d.get('SUBTYPE')}")
    lines.append(f"FCOUNTER: {d.get('FCOUNTER')}")
    lines.append(f"TIMETAG: {d.get('TIMETAG')}")
    lines.append(f"DXxDY: {d.get('DX')} x {d.get('DY')}")
    lines.append(f"STRIDE: {d.get('STRIDE')}")
    lines.append(f"BPP: {d.get('BPP')}")
    lines.append(f"PALTYPE: {d.get('PALTYPE')}")
    if 'PRODINFO' in d:
        lines.append(f"PRODINFO: {d.get('PRODINFO')}")
    if 'TOD' in d:
        lines.append(f"TOD: {d.get('TOD')}")
    lines.append("")
    if 'GEO_TAG_ID' in d:
        lines.append(f"GEO_TAG.ID: {d.get('GEO_TAG_ID')}")
        lines.append(f"GEO_TAG.VALID: {d.get('GEO_TAG_VALID')}")
        lines.append(f"GEO_TAG.SIZE: {d.get('GEO_TAG_SIZE')}")
    if 'ORIENTATION_deg' in d:
        lines.append(f"ORIENTATION: {d.get('ORIENTATION_deg'):.4f} deg ({d.get('ORIENTATION_rad'):.6f} rad)")
    if 'LATITUDE_deg' in d and 'LONGITUDE_deg' in d:
        lines.append(f"LAT,LON: {d.get('LATITUDE_deg'):.6f} deg, {d.get('LONGITUDE_deg'):.6f} deg")
    if 'REF_X' in d:
        lines.append(f"REF_X,REF_Y: {d.get('REF_X')}, {d.get('REF_Y')}")
    if 'SCALE_X' in d:
        lines.append(f"SCALE_X,SCALE_Y: {d.get('SCALE_X'):.6f}, {d.get('SCALE_Y'):.6f}")
    if 'PIXEL_TAG_ID' in d:
        lines.append(f"PIXEL_TAG.ID: {d.get('PIXEL_TAG_ID')} VALID={d.get('PIXEL_TAG_VALID')} SIZE={d.get('PIXEL_TAG_SIZE')}")
    return "\n".join(lines)
