"""Manual smoke scenario for end-to-end pipeline check without GUI."""

from __future__ import annotations

import argparse
import ctypes
import ctypes.util
import os
from pathlib import Path
import sys
import time

import numpy as np

PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from python_app.models.dataset_model import ComboKey, SweepCollection, TraceData
from python_app.models.run_config_model import RunConfigModel
from python_app.orchestration.config_writer import ConfigWriter
from python_app.orchestration.process_supervisor import ProcessSupervisor
from python_app.orchestration.shm_reader import ShmRingReader
from python_app.storage.npz_store import NpzStore, radar_key_from_config


def _make_unique_ring_name(prefix: str) -> str:
    """Build unique POSIX SHM ring name."""
    stamp = time.monotonic_ns()
    return f"/{prefix}_{os.getpid()}_{stamp}"


def _shm_unlink(name: str) -> None:
    """Best-effort unlink for POSIX shared-memory object."""
    libc_name = ctypes.util.find_library("c")
    if libc_name is None:
        return

    libc = ctypes.CDLL(libc_name, use_errno=True)
    libc.shm_unlink.argtypes = [ctypes.c_char_p]
    libc.shm_unlink.restype = ctypes.c_int

    result = libc.shm_unlink(name.encode("utf-8"))
    if result == 0:
        return

    err = ctypes.get_errno()
    if err != 2:  # ENOENT
        raise OSError(err, f"shm_unlink failed for {name}")


def build_synthetic_collection(
    config: RunConfigModel,
    value_scale: float,
    *,
    s11_scale: float,
    s11_phase_offset: float,
) -> SweepCollection:
    """Build synthetic sweep collection for all configured switch combos."""
    traces: list[TraceData] = []
    combos = RunConfigModel.build_full_combos(config.input_switch.positions, config.output_switch.positions)

    for combo in combos:
        frequency_hz = np.linspace(
            config.radar.sweep.start_hz,
            config.radar.sweep.stop_hz,
            config.radar.sweep.points,
            dtype=np.float32,
        )
        phase = np.linspace(0.0, np.pi * 2.0, config.radar.sweep.points, dtype=np.float32)
        reflected_phase = (phase * 0.6) + s11_phase_offset
        s11 = (s11_scale * (np.cos(reflected_phase) + 1j * np.sin(reflected_phase))).astype(np.complex64)
        s21 = value_scale * (np.cos(phase) + 1j * np.sin(phase)).astype(np.complex64)

        traces.append(
            TraceData(
                combo=ComboKey(input=combo.input, output=combo.output),
                frequency_hz=frequency_hz,
                s11=s11,
                s21=s21,
            )
        )

    return SweepCollection(collection_id=1, monotonic_ns=time.monotonic_ns(), traces=traces)


def main() -> int:
    """Run manual smoke-test pipeline scenario."""
    parser = argparse.ArgumentParser()
    parser.add_argument("--duration", type=float, default=3.0)
    args = parser.parse_args()

    project_root = PROJECT_ROOT
    store = NpzStore(project_root / "python_app/data")
    config_writer = ConfigWriter(project_root / "python_app/runtime")
    supervisor = ProcessSupervisor(project_root)

    config = RunConfigModel.load_from_path(project_root / "run_config.json")
    config.radar.driver_mode = "mock"
    config.input_switch.driver_mode = "mock"
    config.output_switch.driver_mode = "mock"
    config.combos = RunConfigModel.build_full_combos(config.input_switch.positions, config.output_switch.positions)
    config.rings.raw.name = _make_unique_ring_name("radar_raw_smoke")
    config.rings.raw_tap.name = _make_unique_ring_name("radar_raw_tap_smoke")
    config.rings.preprocessed.name = _make_unique_ring_name("radar_preprocessed_smoke")
    config.rings.preprocessed_tap.name = _make_unique_ring_name("radar_preprocessed_tap_smoke")
    config.rings.results.name = _make_unique_ring_name("radar_results_smoke")

    radar_key = radar_key_from_config(
        model_name=config.radar.model,
        serial=config.radar.serial,
        sweep_start_hz=config.radar.sweep.start_hz,
        sweep_stop_hz=config.radar.sweep.stop_hz,
        sweep_points=config.radar.sweep.points,
        ifbw_hz=config.radar.sweep.if_bandwidth_hz,
        power_dbm=config.radar.sweep.power_dbm,
        extra_serials=config.radar_key_extra_parts() or None,
    )

    s21_calibration_set = build_synthetic_collection(config, value_scale=1.0, s11_scale=0.15, s11_phase_offset=0.4)
    s21_reference_set = build_synthetic_collection(config, value_scale=0.3, s11_scale=0.08, s11_phase_offset=0.9)
    s11_open_set = build_synthetic_collection(config, value_scale=0.85, s11_scale=0.95, s11_phase_offset=0.1)
    s11_short_set = build_synthetic_collection(config, value_scale=0.85, s11_scale=0.95, s11_phase_offset=3.2)
    s11_load_set = build_synthetic_collection(config, value_scale=0.85, s11_scale=0.05, s11_phase_offset=1.4)
    s11_reference_set = build_synthetic_collection(config, value_scale=0.3, s11_scale=0.18, s11_phase_offset=1.1)

    store.save_set("s21_calibration", radar_key, "smoke_cal", s21_calibration_set)
    store.save_set("s21_reference", radar_key, "smoke_ref", s21_reference_set)
    store.save_set("s11_open", radar_key, "smoke_open", s11_open_set)
    store.save_set("s11_short", radar_key, "smoke_short", s11_short_set)
    store.save_set("s11_load", radar_key, "smoke_load", s11_load_set)
    store.save_set("s11_reference", radar_key, "smoke_s11_ref", s11_reference_set)

    config.preprocess.s21.calibration.set_name = "smoke_cal"
    config.preprocess.s21.reference.set_name = "smoke_ref"
    config.preprocess.s11.calibration.open.set_name = "smoke_open"
    config.preprocess.s11.calibration.short.set_name = "smoke_short"
    config.preprocess.s11.calibration.load.set_name = "smoke_load"
    config.preprocess.s11.reference.set_name = "smoke_s11_ref"
    config_writer.prepare_preprocess_bundles(store, radar_key, config)

    config_path = config_writer.write(config, project_root / "python_app/runtime/run_config_smoke.json")

    result_reader: ShmRingReader | None = None
    try:
        supervisor.start(config_path)
        result_reader = ShmRingReader(config.rings.results.name)
        deadline = time.monotonic() + args.duration
        received = 0

        while time.monotonic() < deadline:
            result = result_reader.pop_result_collection() if result_reader is not None else None
            if result is not None:
                received += 1
            time.sleep(0.02)

        print(f"Received result collections: {received}")
    finally:
        supervisor.stop_all()
        if result_reader is not None:
            result_reader.close()
        _shm_unlink(config.rings.raw.name)
        _shm_unlink(config.rings.raw_tap.name)
        _shm_unlink(config.rings.preprocessed.name)
        _shm_unlink(config.rings.preprocessed_tap.name)
        _shm_unlink(config.rings.results.name)

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
