import numpy

from ...bliss_globals import (
    setup_globals,  # pyright: ignore[reportAttributeAccessIssue]
)
from ...version_utils import has_minimal_version
from .. import testing
from ..processors.stop_scan_xrpd_id31 import DemoStopIntegrateSum
from ..processors.stop_scan_xrpd_id31 import id31_patching


def _measure_baseline_imax(expo: float = 0.2) -> float:
    """Return the max pixel value from a single ct with no attenuation."""
    # id31_patching:
    # - ensures setup_globals.atten is available;
    # - ensures high energy so that attenuation with SiO2 performs as expected
    #   this is the id31.attenuator.Attenuator (`setup_globals.atten` on the beamline)
    # *note* it is called again in DemoStopIntegrateSum
    id31_patching(energy=75.05)

    setup_globals.atten.bits = 0
    scan = setup_globals.ct(expo, setup_globals.difflab6)
    return float(numpy.nanmax(scan.streams["difflab6:image"][:]))


@testing.integration_test
def test_attenuation_increase():
    """Verify that the preset increases attenuation when frames are saturating.

    Strategy: set ``detector_saturation`` below the measured baseline intensity
    so every frame appears saturating to the preset.  The workflow threshold is
    set astronomically high so the Ewoks workflow never fires a stop event and
    the scan runs to normal completion.  After ``npoints`` frames the preset
    must have moved ``atten.bits`` above 0.
    """
    if not has_minimal_version("bliss", "2.2"):
        testing.skip_integration_test("Requires bliss>=2.2")

    detector = setup_globals.difflab6
    expo = 0.2
    npoints = 5

    baseline_imax = _measure_baseline_imax(expo)

    # saturating threshold: set saturation well below the real detector output
    saturation = baseline_imax * 0.5
    p = DemoStopIntegrateSum(
        workflow_threshold=1e15,  # never trigger stop
        detector_name=detector.name,
        detector_saturation=saturation,
        frame_target_max=saturation * 0.9,
        frame_target_min=saturation * 0.1,
    )
    setup_globals.atten.bits = 0  # start with no attenuation

    scan = setup_globals.loopscan(npoints, expo, detector, run=False, save=False)
    scan.acq_chain.add_preset(p)
    scan.run()

    assert (
        setup_globals.atten.bits > 0
    ), f"Expected atten.bits > 0 after saturating frames, got {setup_globals.atten.bits}"


@testing.integration_test
def test_attenuation_decrease():
    """Verify that the preset decreases attenuation when frames are too dim.

    Strategy: start with high attenuation (``bits = 10``) and set
    ``frame_target_min`` well above the measured baseline intensity so every
    frame appears too dim.  ``detector_saturation`` is set high enough that
    removing attenuation is predicted to be safe.  The preset must lower
    ``atten.bits`` below the initial value.
    """
    if not has_minimal_version("bliss", "2.2"):
        testing.skip_integration_test("Requires bliss>=2.2")

    detector = setup_globals.difflab6
    expo = 0.2
    npoints = 5
    initial_bits = 10

    baseline_imax = _measure_baseline_imax(expo)

    # dim threshold: target_min above what the detector actually returns
    saturation = baseline_imax * 20
    p = DemoStopIntegrateSum(
        workflow_threshold=1e15,  # never trigger stop
        detector_name=detector.name,
        detector_saturation=saturation,
        frame_target_max=saturation * 0.9,
        frame_target_min=baseline_imax * 2,  # above measured baseline: always too dim
    )
    setup_globals.atten.bits = initial_bits

    scan = setup_globals.loopscan(npoints, expo, detector, run=False, save=False)
    scan.acq_chain.add_preset(p)
    scan.run()

    assert setup_globals.atten.bits < initial_bits, (
        f"Expected atten.bits < {initial_bits} after dim frames, "
        f"got {setup_globals.atten.bits}"
    )


@testing.integration_test
def stop_scan_pyfai_demo():
    if not has_minimal_version("bliss", "2.2"):
        testing.skip_integration_test("Requires bliss>=2.2")

    # set detector object and exposure time
    detector = setup_globals.difflab6
    expo = 0.2

    # create the preset; the Demo version sets up:
    # - the ID31 mock attenuator,
    # - demo PyFAI config,
    # - demo Ewoks workflow
    p = DemoStopIntegrateSum(workflow_threshold=0.005, detector_name=detector.name)

    # run a long loopscan with the preset - should stop at 10-15 frames
    scan = setup_globals.loopscan(100, expo, detector, run=False)
    scan.acq_chain.add_preset(p)
    scan.run()

    # check that the scan stopped early as expected
    npoints = len(scan.streams["difflab6:image"])
    assert npoints < 25, f"Stream 'difflab6:image' has {npoints} points, expected < 25"
