"""ISIS cube loading and orientation normalization.

Loads ISIS .cub files as xarray DataArrays with proper CRS metadata
and corrected display orientation (north-up for map-projected cubes).
"""

from __future__ import annotations

from pathlib import Path

import pvl
import rioxarray  # noqa: F401 — registers the .rio accessor
import xarray as xr


def read_label(cube_path: str | Path) -> pvl.PVLModule:
    """Read the PVL label from an ISIS cube file.

    Parameters
    ----------
    cube_path : path-like
        Path to the .cub file.

    Returns
    -------
    pvl.PVLModule
        Parsed PVL label.
    """
    return pvl.load(str(cube_path))


def get_projection_info(label: pvl.PVLModule) -> dict | None:
    """Extract map projection information from a cube label.

    Returns None for level-1 (unprojected) cubes.
    """
    try:
        mapping = label["IsisCube"]["Mapping"]
        return dict(mapping)
    except (KeyError, TypeError):
        return None


def get_cube_level(label: pvl.PVLModule) -> int:
    """Determine processing level of an ISIS cube.

    Returns
    -------
    int
        1 for unprojected (raw geometry), 2 for map-projected.
    """
    return 2 if get_projection_info(label) is not None else 1


def load_cube(cube_path: str | Path) -> xr.DataArray:
    """Load an ISIS cube as an xarray DataArray.

    For level-2 (map-projected) cubes, the CRS is set from the
    Mapping group and the image is oriented north-up regardless
    of the original detector readout direction.

    For level-1 cubes, the image is returned in sample/line space
    with label metadata attached as attributes.

    Parameters
    ----------
    cube_path : path-like
        Path to the .cub file.

    Returns
    -------
    xr.DataArray
        Image data with metadata in .attrs.
    """
    cube_path = Path(cube_path)
    if not cube_path.exists():
        raise FileNotFoundError(f"Cube not found: {cube_path}")

    label = read_label(cube_path)

    # rioxarray/rasterio can read ISIS cubes via GDAL's ISIS3 driver
    da = xr.open_dataarray(cube_path, engine="rasterio")

    # Squeeze single-band cubes for simpler downstream handling
    if "band" in da.dims and da.sizes["band"] == 1:
        da = da.squeeze("band", drop=True)

    # Attach useful metadata
    da.attrs["cube_path"] = str(cube_path)
    da.attrs["cube_level"] = get_cube_level(label)
    da.attrs["label"] = label

    proj_info = get_projection_info(label)
    if proj_info is not None:
        da.attrs["projection_info"] = proj_info

    # Normalize orientation for level-2 cubes.
    # ISIS cubes can have flipped line direction depending on the
    # instrument and projection settings. We ensure consistent
    # north-up display by checking the y-coordinate ordering.
    if da.attrs["cube_level"] == 2 and "y" in da.dims:
        y_vals = da.coords["y"].values
        if len(y_vals) > 1 and y_vals[0] < y_vals[-1]:
            # y increases downward — flip to north-up
            da = da.isel(y=slice(None, None, -1))

    return da


def build_serial_lookup(
    cube_paths: list[Path],
) -> dict[str, Path]:
    """Build a mapping from control network serial numbers to cube paths.

    Reads the SpacecraftClockCount from each cube label and matches it
    against the clock count portion of ISIS serial numbers
    (e.g., ``MRO/CTX/0910464726:234``).
    """
    clock_to_path: dict[str, Path] = {}
    for cp in cube_paths:
        try:
            label = pvl.load(str(cp))
            inst = label["IsisCube"]["Instrument"]
            clock = str(
                inst.get("SpacecraftClockCount",
                         inst.get("SpacecraftClockStartCount", ""))
            )
            if clock:
                clock_to_path[clock] = cp
        except Exception:
            continue

    return clock_to_path


def match_serials_to_cubes(
    serial_numbers: list[str],
    cube_paths: list[Path],
) -> dict[str, Path]:
    """Match serial numbers from a control network to cube file paths.

    Returns a dict mapping serial number -> cube path.
    """
    clock_to_path = build_serial_lookup(cube_paths)
    result: dict[str, Path] = {}
    for sn in serial_numbers:
        # Serial numbers are like MRO/CTX/0910464726:234
        # The clock count is the last segment
        clock = sn.rsplit("/", 1)[-1]
        if clock in clock_to_path:
            result[sn] = clock_to_path[clock]
    return result


def get_serial_number(label: pvl.PVLModule) -> str:
    """Construct a serial number proxy from label metadata.

    ISIS serial numbers are typically generated by `serialnumber`
    or stored in the cube's history. This extracts enough info
    to match against control network serial numbers.

    For a proper serial number, use ISIS's `sn` command or the
    SerialNumberList functionality.

    Returns
    -------
    str
        Instrument-derived identifier string.
    """
    inst = label["IsisCube"].get("Instrument", {})
    archive = label["IsisCube"].get("Archive", {})

    # Try common patterns
    spacecraft = inst.get("SpacecraftName", inst.get("SpacecraftId", "Unknown"))
    instrument_id = inst.get("InstrumentId", "Unknown")
    start_time = inst.get("StartTime", inst.get("SpacecraftClockStartCount", "Unknown"))

    return f"{spacecraft}/{instrument_id}/{start_time}"
