"""Unit tests for validating stack classification logic in AutofocusThing.

This module tests whether the `check_stack_result` method correctly classifies
z-stack sharpness profiles into "success", "continue", or "restart" categories
based on predefined test cases.

Sharpness profiles are loaded from a JSON file and converted into mock capture
objects to simulate real camera captures.
"""

import json
import os

import pytest

from labthings_fastapi.testing import create_thing_without_server

from openflexure_microscope_server.things.autofocus import AutofocusThing

THIS_DIR = os.path.dirname(__file__)
DATA_PATH = os.path.join(THIS_DIR, "data", "sharpness_test_cases.json")


class MockCapture:
    """Simple mock object representing a captured image.

    This class mimics the minimal interface required by
    `check_stack_result` by adding the `sharpness` and `buffer_id`
    attributes.

    :param sharpness: The sharpness value associated with the image.
    :param buffer_id: A unique identifier for the image buffer.
    """

    def __init__(self, sharpness, buffer_id):
        """Give each capture a sharpness and buffer_id."""
        self.sharpness = sharpness
        self.buffer_id = buffer_id


def make_captures(sharpness_list):
    """Convert a list of sharpness values into mock capture objects.

    :param sharpness_list: A list of numeric sharpness values.
    :returns: A list of MockCapture instances with sequential buffer IDs.
    """
    return [MockCapture(s, i) for i, s in enumerate(sharpness_list)]


def load_cases():
    """Load the sharpness data from the json.

    Includes the sharpnesses to test, manually written labels
    on the required result, and whether the test is allowed to
    fail. This ensures future tests will flag any regression on
    tests while allowing improvements.
    """
    with open(DATA_PATH) as f:
        data = json.load(f)

    params = []
    for i, case in enumerate(data):
        expected = case["label"]

        # Allow multiple acceptable labels
        if not isinstance(expected, list):
            expected = [expected]

        marks = []

        if case.get("allow_failure", False):
            marks.append(pytest.mark.xfail(reason="Known failing case"))

        params.append(
            pytest.param(
                case["sharpnesses"],
                expected,
                marks=marks,
                id=f"case_{i}",
            )
        )
    return params


@pytest.mark.parametrize(("sharpnesses", "expected"), load_cases())
def test_stack_labelling(sharpnesses, expected):
    """Test stack classification accuracy against labelled sharpness cases.

    This test loads the test from load_cases and evaluates
    the classification returned by `check_stack_result`.

    The test ensures that only cases marked with "allow_failure: true" can fail.

    Expected labels may be a single value or a list of acceptable values.
    """
    autofocus_thing = create_thing_without_server(AutofocusThing, mock_all_slots=True)

    captures = make_captures(sharpnesses)

    result, _ = autofocus_thing.check_stack_result(captures, check_turning_points=False)

    assert result in expected
