"""Tests for measurements router (/api/measurements)."""
import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from src.backend.models.orm.user import User
from src.backend.models.orm.recipe import RecipeVersion
from src.backend.models.orm.task import RecipeTask, RecipeSubtask
from src.backend.tests.conftest import auth_headers, create_test_recipe


async def _get_subtask_and_version(
    client: AsyncClient, user: User, recipe_id: int
) -> tuple[int, int]:
    """Helper: return (subtask_id, version_id) from the recipe's current version."""
    tasks_resp = await client.get(
        f"/api/recipes/{recipe_id}/tasks", headers=auth_headers(user)
    )
    task = tasks_resp.json()[0]
    subtask_id = task["subtasks"][0]["id"]
    version_id = task["version_id"]
    return subtask_id, version_id


class TestCreateMeasurement:
    """POST /api/measurements/ tests."""

    async def test_create_measurement(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        db_session: AsyncSession,
    ):
        """MeasurementTec can create a measurement."""
        recipe = await create_test_recipe(
            db_session, measurement_tec_user.id
        )
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        resp = await client.post(
            "/api/measurements/",
            headers=auth_headers(measurement_tec_user),
            json={
                "subtask_id": subtask_id,
                "version_id": version_id,
                "value": 10.1,
                "input_method": "manual",
            },
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["value"] == pytest.approx(10.1, rel=1e-4)
        assert data["pass_fail"] in ("pass", "warning", "fail")
        assert data["measured_by"] == measurement_tec_user.id

    async def test_measurement_requires_auth(self, client: AsyncClient):
        """Creating measurement without auth returns 401."""
        resp = await client.post(
            "/api/measurements/",
            json={
                "subtask_id": 1,
                "version_id": 1,
                "value": 10.0,
            },
        )
        assert resp.status_code == 401

    async def test_measurement_validation(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
    ):
        """Measurement with non-existent subtask returns 400."""
        resp = await client.post(
            "/api/measurements/",
            headers=auth_headers(measurement_tec_user),
            json={
                "subtask_id": 99999,
                "version_id": 99999,
                "value": 10.0,
            },
        )
        assert resp.status_code == 400


class TestMeasurementPassFail:
    """Pass/fail calculation tests."""

    async def test_measurement_pass(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        db_session: AsyncSession,
    ):
        """Value within tolerance -> pass."""
        recipe = await create_test_recipe(
            db_session, measurement_tec_user.id
        )
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        # nominal=10.0, utl=10.5, uwl=10.3, lwl=9.7, ltl=9.5
        resp = await client.post(
            "/api/measurements/",
            headers=auth_headers(measurement_tec_user),
            json={
                "subtask_id": subtask_id,
                "version_id": version_id,
                "value": 10.0,
            },
        )
        assert resp.status_code == 200
        assert resp.json()["pass_fail"] == "pass"

    async def test_measurement_warning(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        db_session: AsyncSession,
    ):
        """Value outside warning but inside tolerance -> warning."""
        recipe = await create_test_recipe(
            db_session, measurement_tec_user.id
        )
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        # uwl=10.3 < 10.4 < utl=10.5 -> warning
        resp = await client.post(
            "/api/measurements/",
            headers=auth_headers(measurement_tec_user),
            json={
                "subtask_id": subtask_id,
                "version_id": version_id,
                "value": 10.4,
            },
        )
        assert resp.status_code == 200
        assert resp.json()["pass_fail"] == "warning"

    async def test_measurement_fail(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        db_session: AsyncSession,
    ):
        """Value outside tolerance limits -> fail."""
        recipe = await create_test_recipe(
            db_session, measurement_tec_user.id
        )
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        # value > utl=10.5 -> fail
        resp = await client.post(
            "/api/measurements/",
            headers=auth_headers(measurement_tec_user),
            json={
                "subtask_id": subtask_id,
                "version_id": version_id,
                "value": 10.6,
            },
        )
        assert resp.status_code == 200
        assert resp.json()["pass_fail"] == "fail"


class TestListMeasurements:
    """GET /api/measurements/ tests."""

    async def test_list_measurements(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        metrologist_user: User,
        db_session: AsyncSession,
    ):
        """Metrologist can list measurements with pagination."""
        recipe = await create_test_recipe(
            db_session, measurement_tec_user.id
        )
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        # Create some measurements
        for val in [9.8, 10.0, 10.2]:
            await client.post(
                "/api/measurements/",
                headers=auth_headers(measurement_tec_user),
                json={
                    "subtask_id": subtask_id,
                    "version_id": version_id,
                    "value": val,
                },
            )

        # List as Metrologist
        resp = await client.get(
            "/api/measurements/",
            headers=auth_headers(metrologist_user),
        )
        assert resp.status_code == 200
        data = resp.json()
        assert "items" in data
        assert "total" in data
        assert data["total"] >= 3

    async def test_measurement_tec_can_list_measurements(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        db_session: AsyncSession,
    ):
        """Regression: MeasurementTec must be able to list measurements.

        The Flask client renders task_complete.html by calling GET
        /api/measurements?version_id=X right after the operator finishes a
        recipe. The endpoint used to require Metrologist, which silently
        produced an empty riepilogo for every operator without that role.
        """
        recipe = await create_test_recipe(db_session, measurement_tec_user.id)
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        for val in [9.8, 10.0, 10.2]:
            await client.post(
                "/api/measurements/",
                headers=auth_headers(measurement_tec_user),
                json={
                    "subtask_id": subtask_id,
                    "version_id": version_id,
                    "value": val,
                },
            )

        resp = await client.get(
            f"/api/measurements/?version_id={version_id}",
            headers=auth_headers(measurement_tec_user),
        )
        assert resp.status_code == 200, resp.text
        data = resp.json()
        assert data["total"] >= 3

    async def test_measurement_by_recipe_filter(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        metrologist_user: User,
        db_session: AsyncSession,
    ):
        """Filtering measurements by recipe_id returns only matching records."""
        recipe = await create_test_recipe(
            db_session, measurement_tec_user.id
        )
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        await client.post(
            "/api/measurements/",
            headers=auth_headers(measurement_tec_user),
            json={
                "subtask_id": subtask_id,
                "version_id": version_id,
                "value": 10.0,
            },
        )

        resp = await client.get(
            "/api/measurements/",
            headers=auth_headers(metrologist_user),
            params={"recipe_id": recipe.id},
        )
        assert resp.status_code == 200
        assert resp.json()["total"] >= 1


class TestMeasurementStatistics:
    """Measurement deviation / statistics edge case tests."""

    async def test_measurement_has_deviation(
        self,
        client: AsyncClient,
        measurement_tec_user: User,
        db_session: AsyncSession,
    ):
        """Measurement response includes deviation from nominal."""
        recipe = await create_test_recipe(
            db_session, measurement_tec_user.id
        )
        subtask_id, version_id = await _get_subtask_and_version(
            client, measurement_tec_user, recipe.id
        )

        resp = await client.post(
            "/api/measurements/",
            headers=auth_headers(measurement_tec_user),
            json={
                "subtask_id": subtask_id,
                "version_id": version_id,
                "value": 10.2,
            },
        )
        assert resp.status_code == 200
        data = resp.json()
        # deviation = 10.2 - nominal(10.0) = 0.2
        assert data["deviation"] is not None
        assert abs(data["deviation"] - 0.2) < 0.001
