"""Helpers for B-scan history signatures and cache rebuilding."""

from __future__ import annotations

from collections import deque

import numpy as np

from python_app.models.dataset_model import ResultCollection
from python_app.orchestration.live_processing_config import ProcessingLiveConfig


def _result_tail(
    *,
    result_history: list[ResultCollection],
    history_limit: int,
    floor_collection_id: int,
) -> list[ResultCollection]:
    """Return filtered and de-duplicated result-history tail for B-scan usage."""
    filtered = [
        collection
        for collection in result_history[-history_limit:]
        if int(collection.collection_id) > int(floor_collection_id)
    ]
    unique_tail: list[ResultCollection] = []
    seen_keys: set[tuple[int, int]] = set()
    for collection in filtered:
        key = (int(collection.collection_id), int(collection.monotonic_ns))
        if key in seen_keys:
            continue
        seen_keys.add(key)
        unique_tail.append(collection)
    return unique_tail


def build_bscan_signature(
    live_config: ProcessingLiveConfig,
    result_history: list[ResultCollection],
    history_limit: int,
    floor_collection_id: int,
) -> tuple[object, ...]:
    """Build deterministic signature used to detect B-scan cache invalidation."""
    result_tail = _result_tail(
        result_history=result_history,
        history_limit=history_limit,
        floor_collection_id=floor_collection_id,
    )
    return (
        str(live_config.bscan_axis),
        float(live_config.bscan_cut_m),
        float(live_config.bscan_max_depth_m),
        float(live_config.bscan_gain),
        float(live_config.bscan_start_freq_mhz),
        float(live_config.bscan_stop_freq_mhz),
        int(floor_collection_id),
        tuple((int(collection.collection_id), int(collection.monotonic_ns), len(collection.blocks)) for collection in result_tail),
    )


def rebuild_bscan_history_from_results(
    result_history: list[ResultCollection],
    history_limit: int,
    floor_collection_id: int,
) -> tuple[dict[tuple[int, int], deque[np.ndarray]], dict[tuple[int, int], np.ndarray]]:
    """Rebuild B-scan history and depth axes from processed result payloads."""
    history_by_combo: dict[tuple[int, int], deque[np.ndarray]] = {}
    depth_axis_by_combo: dict[tuple[int, int], np.ndarray] = {}

    result_tail = _result_tail(
        result_history=result_history,
        history_limit=history_limit,
        floor_collection_id=floor_collection_id,
    )

    for collection in result_tail:
        for block in collection.blocks:
            key = (block.combo.input_pos, block.combo.output_pos)
            for payload in block.payloads:
                if payload.kind != 1 or payload.processing_name != "bscan":
                    continue
                if payload.frequency_hz.size == 0 or payload.trace.size == 0:
                    continue
                if payload.frequency_hz.size != payload.trace.size:
                    continue

                depth_axis = np.asarray(payload.frequency_hz, dtype=np.float32)
                amplitudes = np.asarray(np.real(payload.trace), dtype=np.float32)
                if depth_axis.size == 0 or amplitudes.size == 0:
                    continue

                history = history_by_combo.get(key)
                stored_axis = depth_axis_by_combo.get(key)
                if (
                    history is None
                    or stored_axis is None
                    or stored_axis.shape != depth_axis.shape
                    or not np.allclose(stored_axis, depth_axis, rtol=1e-4, atol=1e-6)
                ):
                    history = deque(maxlen=history_limit)
                    history_by_combo[key] = history
                    depth_axis_by_combo[key] = depth_axis.copy()

                history.append(amplitudes.copy())

    return history_by_combo, depth_axis_by_combo


def pick_bscan_display_key(
    history_by_combo: dict[tuple[int, int], deque[np.ndarray]],
) -> tuple[int, int] | None:
    """Choose combo key to display when multiple histories are present."""
    if not history_by_combo:
        return None
    return next(iter(history_by_combo.keys()))
