"""
Tests for XMLGenerator.generate — the sync_v2.xml writer.

Covers the block-emission contract:
  * blocks are emitted for array indices 1..nb (seed at index 0 is represented
    by the header tag), so the last real block is never dropped;
  * ADC is reported as a single event per continuous HIGH run, and its window
    duration equals the full (un-truncated) intended duration;
  * the gradient system goes HIGH on the very last event;
  * every emitted CL value is within [20, 999999] ticks.
"""
from __future__ import annotations

import xml.etree.ElementTree as ET

from seq_interp.src.core.synchronizer import Synchronizer
from seq_interp.src.hardware.constraints import HardwareConstraints
from seq_interp.src.interfaces.xml_generator import XMLGenerator

MIN_TICKS = 20
MAX_TICKS = 999999


def _hw(**flags) -> HardwareConstraints:
    hw = HardwareConstraints()
    for k, v in flags.items():
        setattr(hw, k, v)
    return hw


def _generate(make_seq, specs, tmp_path, **flags):
    """Run synchronizer + generator, return (parsed_channels, adc_vals, adc_starts)."""
    hw = _hw(**flags)
    sync_data = Synchronizer(hw).process(make_seq(specs))
    path = tmp_path / "sync_v2.xml"
    adc_vals, adc_starts = XMLGenerator().generate(sync_data, str(path), hw)

    root = ET.parse(str(path)).getroot()
    channels = {}
    for ch in ("RF", "SW", "ADC", "GR", "CL"):
        node = root.find(ch)
        channels[ch] = [int(child.text) for child in node]
    channels["ParamCount"] = int(root.find("ParamCount").text)
    return channels, adc_vals, adc_starts


# --------------------------------------------------------------------------- #
# Emission contract / off-by-one regression
# --------------------------------------------------------------------------- #

def test_param_count_and_channel_lengths(make_seq, tmp_path):
    ch, _, _ = _generate(
        make_seq,
        [{"rf": True, "dur": 1e-3}, {"adc": True, "dur": 2e-3}],
        tmp_path,
    )
    n = ch["ParamCount"]
    for name in ("RF", "SW", "ADC", "GR", "CL"):
        assert len(ch[name]) == n, name


def test_trailing_adc_block_is_emitted(make_seq, tmp_path):
    """Regression: a sequence whose LAST block is ADC must still emit ADC=1."""
    ch, adc_vals, _ = _generate(make_seq, [{"adc": True, "dur": 2e-3}], tmp_path)
    assert sum(ch["ADC"]) >= 1            # the ADC HIGH is not dropped
    assert len(adc_vals) == 1            # exactly one ADC event


# --------------------------------------------------------------------------- #
# ADC single event + exact window
# --------------------------------------------------------------------------- #

def test_split_adc_single_event_full_window(make_seq, tmp_path):
    """A 50 ms ADC block split into parts is ONE event spanning the full 50 ms."""
    ch, adc_vals, _ = _generate(make_seq, [{"adc": True, "dur": 50e-3}], tmp_path)
    assert sum(ch["ADC"]) >= 2           # multiple HIGH columns (was split)
    assert len(adc_vals) == 1            # but a single ADC event / trigger
    assert adc_vals[0] == 50e-3          # full window, not a truncated part


def test_adc_window_not_truncated_by_min_clamp(make_seq, tmp_path):
    """Min-clamp compensation must not steal duration from the ADC block."""
    _, adc_vals, _ = _generate(
        make_seq,
        [{"rf": True, "dur": 1e-3}, {"adc": True, "dur": 2e-3}],
        tmp_path,
        TR_DELAY_ENABLED=True,
    )
    assert len(adc_vals) == 1
    assert adc_vals[0] == 2e-3


# --------------------------------------------------------------------------- #
# Gradient system: last event HIGH
# --------------------------------------------------------------------------- #

def test_gradient_high_on_last_event(make_seq, tmp_path):
    ch, _, _ = _generate(
        make_seq,
        [{"rf": True, "dur": 1e-3}, {"dur": 5e-3}, {"adc": True, "dur": 2e-3}],
        tmp_path,
    )
    assert ch["GR"][-1] == 1                       # last event HIGH
    assert all(v == 0 for v in ch["GR"][1:-1])     # all middle blocks LOW


# --------------------------------------------------------------------------- #
# CL bounds
# --------------------------------------------------------------------------- #

def test_cl_values_within_bounds(make_seq, tmp_path):
    ch, _, _ = _generate(
        make_seq,
        [{"rf": True, "dur": 1e-3}, {"adc": True, "dur": 50e-3}],
        tmp_path,
    )
    assert min(ch["CL"]) >= MIN_TICKS
    assert max(ch["CL"]) <= MAX_TICKS
