"""Snapshot and ring-drain helper mixin for the main GUI window."""

from __future__ import annotations

from pathlib import Path
import time

from PyQt6.QtWidgets import QFileDialog
from python_app.gui.runtime.history import record_result_history


class AppWindowSnapshotMixin:
    """Saves runtime data snapshots and maintains ring-reader freshness."""

    def _save_snapshot(self) -> None:
        """Save runtime snapshot in numpy-directory format."""
        self._drain_runtime_rings_for_snapshot()

        if not self._raw_history and not self._pre_history and not self._result_history:
            self._show_error("No runtime data is available for save")
            return

        try:
            last_n = int(self._save_count.value())
            output_root = Path(self._save_path_input.text().strip()).expanduser()
            snapshot_name = self._save_name_input.text().strip()
            snapshot_dir, summary = self._store.save_runtime_snapshot_numpy(
                output_root,
                snapshot_name,
                list(self._raw_history),
                list(self._pre_history),
                list(self._result_history),
                last_n,
            )
            self._log(
                f"Saved numpy snapshot: {snapshot_dir} "
                f"(raw={summary.get('raw_count', 0)}, "
                f"preprocessed={summary.get('preprocessed_count', 0)}, "
                f"results={summary.get('result_count', 0)}, "
                f"mode={summary.get('selection_mode', 'unknown')}, "
                f"anchor={summary.get('anchor_stage', 'unknown')}, "
                f"aligned_keys={summary.get('aligned_key_count', 0)}, "
                f"raw_missing={summary.get('raw_missing_count', 0)}, "
                f"pre_missing={summary.get('preprocessed_missing_count', 0)}, "
                f"result_missing={summary.get('result_missing_count', 0)}, "
                f"requested_last_n={last_n})"
            )
        except Exception as exc:  # noqa: BLE001
            self._show_error(f"Failed to save snapshot: {exc}")

    def _save_vna_history_json(self) -> None:
        """Save runtime history as vna_system-compatible JSON file."""
        self._drain_runtime_rings_for_snapshot()

        if not self._raw_history and not self._pre_history and not self._result_history:
            self._show_error("No runtime data is available for save")
            return

        try:
            last_n = int(self._save_count.value())
            input_index = int(self._vna_json_input_index.value())
            output_index = int(self._vna_json_output_index.value())
            output_root = Path(self._save_path_input.text().strip()).expanduser()
            output_name = self._save_name_input.text().strip()
            output_path, summary = self._store.save_runtime_vna_history_json(
                output_root,
                output_name,
                list(self._raw_history),
                list(self._pre_history),
                list(self._result_history),
                last_n,
                input_index=input_index,
                output_index=output_index,
                primary_stage="preprocessed",
            )
            self._log(
                f"Saved VNA history JSON: {output_path} "
                f"(sweeps={summary.get('sweep_count', 0)}, "
                f"raw_records={summary.get('raw_record_count', 0)}, "
                f"preprocessed_records={summary.get('preprocessed_record_count', 0)}, "
                f"raw={summary.get('raw_count', 0)}, "
                f"preprocessed={summary.get('preprocessed_count', 0)}, "
                f"results={summary.get('result_count', 0)}, "
                f"mode={summary.get('selection_mode', 'unknown')}, "
                f"anchor={summary.get('anchor_stage', 'unknown')}, "
                f"input={input_index}, "
                f"output={output_index}, "
                f"requested_last_n={last_n})"
            )
        except Exception as exc:  # noqa: BLE001
            self._show_error(f"Failed to save VNA history JSON: {exc}")

    def _clear_all_runtime_history(self) -> None:
        """Clear all runtime histories, ring backlogs, and processor replay state."""
        if self._capture_session is not None:
            self._show_error("Cannot clear runtime history during active capture sequence")
            return

        resume_acquisition = self._supervisor.is_running()
        dropped_raw = 0
        dropped_pre = 0
        dropped_results = 0

        try:
            if resume_acquisition:
                self._stop_run()

            dropped_raw = self._raw_reader.drop_all() if self._raw_reader is not None else 0
            dropped_pre = self._pre_reader.drop_all() if self._pre_reader is not None else 0
            dropped_results = self._result_reader.drop_all() if self._result_reader is not None else 0

            self._replace_runtime_history(retained_raw=[], retained_pre=[], retained_result=[])
            self._bscan_history_floor_collection_id = 0
            self._clear_bscan_plot_history()

            # Clear processor-side replay cache so newly rendered B-scan starts clean.
            self._write_live_processing_config(history_command="clear_all", bump_history_seq=True)

            if self._supervisor.is_processor_running():
                self._drain_results_until_quiet(timeout_s=0.25, poll_s=0.01)
                if self._result_reader is not None:
                    dropped_results += self._result_reader.drop_all()
            self._result_history.clear()

            self._update_history_indicator()
            self._redraw_after_history_deletion()
            self._log(
                "Runtime history fully cleared: "
                f"dropped raw={dropped_raw}, "
                f"preprocessed={dropped_pre}, "
                f"results={dropped_results}"
            )

            if resume_acquisition:
                self._start_run()
        except Exception as exc:  # noqa: BLE001
            self._show_error(f"Failed to clear runtime history: {exc}")

    def _browse_save_path(self) -> None:
        """Open directory picker for snapshot output path."""
        selected = QFileDialog.getExistingDirectory(
            self,
            "Select Snapshot Directory",
            self._save_path_input.text().strip() or str(self._project_root),
        )
        if selected:
            self._save_path_input.setText(selected)

    def _drain_runtime_rings_for_snapshot(self) -> None:
        """Drain readers before snapshot to reduce partial-history races."""
        if self._raw_reader is None and self._pre_reader is None and self._result_reader is None:
            return
        try:
            start_raw_count = len(self._raw_history)
            start_pre_count = len(self._pre_history)
            start_result_count = len(self._result_history)
            deadline = time.monotonic() + 1.2

            while True:
                progress = False

                if self._raw_reader is not None:
                    for _ in range(self._max_pop_per_snapshot_drain):
                        collection = self._raw_reader.pop_raw_collection()
                        if collection is None:
                            break
                        self._raw_history.append(collection)
                        progress = True

                if self._pre_reader is not None:
                    for _ in range(self._max_pop_per_snapshot_drain):
                        collection = self._pre_reader.pop_preprocessed_collection()
                        if collection is None:
                            break
                        self._pre_history.append(collection)
                        progress = True

                if self._result_reader is not None:
                    for _ in range(self._max_pop_per_snapshot_drain):
                        collection = self._result_reader.pop_result_collection()
                        if collection is None:
                            break
                        record_result_history(self._result_history, collection)
                        progress = True

                if progress:
                    continue

                raw_count = len(self._raw_history)
                pre_count = len(self._pre_history)
                result_count = len(self._result_history)

                missing_raw = self._raw_reader is not None and raw_count == start_raw_count
                missing_pre = self._pre_reader is not None and pre_count == start_pre_count
                missing_results = self._result_reader is not None and result_count == start_result_count
                got_results = result_count > start_result_count
                got_raw_or_pre = raw_count > start_raw_count or pre_count > start_pre_count

                if got_results and (missing_raw or missing_pre) and time.monotonic() < deadline:
                    time.sleep(0.01)
                    continue
                if got_raw_or_pre and missing_results and time.monotonic() < deadline:
                    time.sleep(0.01)
                    continue
                if (
                    self._result_reader is not None
                    and max(raw_count, pre_count) > result_count
                    and time.monotonic() < deadline
                ):
                    time.sleep(0.01)
                    continue
                break
        except Exception as exc:  # noqa: BLE001
            self._log(f"Snapshot drain warning: {exc}")

    def _drop_pending_ring_payloads(self, *, include_results: bool = True) -> None:
        """Drop unread payloads from active readers."""
        dropped_raw = self._raw_reader.drop_all() if self._raw_reader is not None else 0
        dropped_pre = self._pre_reader.drop_all() if self._pre_reader is not None else 0
        dropped_results = 0
        if include_results and self._result_reader is not None:
            dropped_results = self._result_reader.drop_all()

        if dropped_raw or dropped_pre or dropped_results:
            self._log(
                "Single capture ring reset: "
                f"dropped raw={dropped_raw}, "
                f"preprocessed={dropped_pre}, "
                f"results={dropped_results}"
            )
