"""
QThread workers that run heavy operations off the UI thread.
"""
from __future__ import annotations

import os
import tempfile

from PySide6.QtCore import QThread, Signal

from seq_interp.src.hardware.constraints import HardwareConstraints
from seq_interp.src.interfaces.pulseq_adapter import PulseqLoader
from seq_interp.src.core.synchronizer import Synchronizer
from seq_interp.src.interfaces.xml_generator import XMLGenerator
from seq_interp.src.interfaces.rf_exporter import RFExporter
from seq_interp.src.interfaces.gradient_exporter import GradientExporter
from seq_interp.src.interfaces.picoscope_exporter import PicoScopeExporter
from seq_interp.src.interfaces.post_request_generator import PostRequestGenerator


class LoadInterpWorker(QThread):
    """
    Load a .seq file and run the synchronizer in one shot.
    Emits finished(seq_data, sync_data, hw) or error(msg).
    """
    finished = Signal(object, object, object)
    error = Signal(str)
    log_msg = Signal(str)

    def __init__(self, seq_path: str, hw_config_path: str | None = None,
                 hw_overrides: dict | None = None):
        super().__init__()
        self.seq_path = seq_path
        self.hw_config_path = hw_config_path
        self.hw_overrides = hw_overrides or {}

    def run(self):
        try:
            hw = HardwareConstraints(json_path=self.hw_config_path)
            for k, v in self.hw_overrides.items():
                setattr(hw, k, v)

            self.log_msg.emit(f"Loading: {self.seq_path}")
            loader = PulseqLoader(hw)
            seq_data = loader.load(self.seq_path)
            n = len(seq_data.get("blocks", []))
            self.log_msg.emit(f"Loaded {n} blocks (zero-duration removed)")

            self.log_msg.emit("Running synchronizer...")
            sync = Synchronizer(hw)
            sync_data = sync.process(seq_data["sequence"])
            nb = sync_data["number_of_blocks"]
            self.log_msg.emit(f"Sync done: {nb} sync blocks (incl. inserted delays)")

            self.finished.emit(seq_data, sync_data, hw)
        except Exception as exc:
            self.error.emit(f"{type(exc).__name__}: {exc}")


class SyncOnlyWorker(QThread):
    """
    Re-run just the synchronizer on an already-loaded sequence,
    applying new hw_overrides from the GUI controls.
    """
    finished = Signal(object, object)   # sync_data, hw
    error = Signal(str)
    log_msg = Signal(str)

    def __init__(self, seq_data: dict, hw_config_path: str | None,
                 hw_overrides: dict | None = None):
        super().__init__()
        self.seq_data = seq_data
        self.hw_config_path = hw_config_path
        self.hw_overrides = hw_overrides or {}

    def run(self):
        try:
            hw = HardwareConstraints(json_path=self.hw_config_path)
            for k, v in self.hw_overrides.items():
                setattr(hw, k, v)

            self.log_msg.emit("Re-running synchronizer with updated settings...")
            sync = Synchronizer(hw)
            sync_data = sync.process(self.seq_data["sequence"])
            nb = sync_data["number_of_blocks"]
            self.log_msg.emit(f"Sync done: {nb} sync blocks")

            self.finished.emit(sync_data, hw)
        except Exception as exc:
            self.error.emit(f"{type(exc).__name__}: {exc}")


class ExportWorker(QThread):
    """
    Export all artifacts to output_dir using the existing exporter classes.
    Also generates sync XML in memory and returns it as a string for the preview panel.
    """
    finished = Signal(str, str, str)   # output_dir, xml_text, post_json_text
    error = Signal(str)
    log_msg = Signal(str)

    def __init__(self, seq_data: dict, sync_data: dict, hw,
                 output_dir: str):
        super().__init__()
        self.seq_data = seq_data
        self.sync_data = sync_data
        self.hw = hw
        self.output_dir = output_dir

    def run(self):
        import json as _json
        try:
            os.makedirs(self.output_dir, exist_ok=True)

            xml_path = os.path.join(self.output_dir, "sync_v2.xml")
            self.log_msg.emit("Generating sync_v2.xml...")
            xml_gen = XMLGenerator()
            adc_values, adc_starts = xml_gen.generate(
                self.sync_data, xml_path, self.hw
            )
            with open(xml_path, "r", encoding="utf-8") as fh:
                xml_text = fh.read()

            self.log_msg.emit("Exporting RF...")
            RFExporter().export(
                self.seq_data,
                self.seq_data.get("params", {}),
                self.output_dir,
            )

            if all(k in self.seq_data
                   for k in ["gx", "gy", "gz", "t_gx", "t_gy", "t_gz"]):
                self.log_msg.emit("Exporting gradients...")
                GradientExporter().export(
                    self.seq_data,
                    self.seq_data.get("params", {}),
                    self.output_dir,
                )

            self.log_msg.emit("Exporting PicoScope params...")
            PicoScopeExporter().generate(
                adc_values, adc_starts, self.output_dir, self.hw
            )

            # Build the POST manifest via the single canonical generator —
            # NOT a hand-rolled copy. points = int(window_s * srate); a local
            # copy previously emitted raw seconds here and produced points < 1.
            params = self.seq_data.get("params", {})
            post_payload = PostRequestGenerator().build(
                seq_data=self.seq_data,
                adc_values=adc_values,
                sequence_path=params.get("seq_path", "sequence"),
                output_dir=self.output_dir,
                hw_cfg={},
                rf_raster_time=params.get("rf_raster_time", self.hw.rf_raster_time),
            )
            post_text = _json.dumps(post_payload, indent=2, default=str)

            self.log_msg.emit(f"Export complete -> {self.output_dir}")
            self.finished.emit(self.output_dir, xml_text, post_text)
        except Exception as exc:
            self.error.emit(f"{type(exc).__name__}: {exc}")


class XmlPreviewWorker(QThread):
    """
    Generate sync XML in a temp file and return its text without writing to output_dir.
    Used to populate the XML preview panel after interpretation, before export.
    """
    finished = Signal(str, str)   # xml_text, post_json_text (minimal)
    error = Signal(str)

    def __init__(self, sync_data: dict, hw):
        super().__init__()
        self.sync_data = sync_data
        self.hw = hw

    def run(self):
        import json as _json, datetime
        try:
            with tempfile.NamedTemporaryFile(suffix=".xml", delete=False,
                                             mode="w", encoding="utf-8") as tf:
                tmp_path = tf.name
            try:
                xml_gen = XMLGenerator()
                adc_values, adc_starts = xml_gen.generate(
                    self.sync_data, tmp_path, self.hw
                )
                with open(tmp_path, "r", encoding="utf-8") as fh:
                    xml_text = fh.read()
            finally:
                try:
                    os.unlink(tmp_path)
                except OSError:
                    pass

            post_payload = {
                "note": "Preview only - run Export to write actual files",
                "isync_blocks": self.sync_data.get("number_of_blocks"),
                "adc_windows": len(adc_values),
                "adc_durations_s": [float(v) for v in adc_values],
                "adc_starts_s": [float(v) for v in adc_starts],
                "time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
            }
            post_text = _json.dumps(post_payload, indent=2)

            self.finished.emit(xml_text, post_text)
        except Exception as exc:
            self.error.emit(f"{type(exc).__name__}: {exc}")
