import numpy as np
import pytest

from supervision.detection.core import Detections
from supervision.detection.utils.converters import mask_to_rle

SERVERLESS_SAM3_DICT = {
    "prompt_results": [
        {
            "prompt_index": 0,
            "echo": {
                "prompt_index": 0,
                "type": "text",
                "text": "person",
                "num_boxes": 0,
            },
            "predictions": [
                {
                    "masks": [[[295, 675], [294, 676]], [[496, 617], [495, 618]]],
                    "confidence": 0.94921875,
                    "format": "polygon",
                }
            ],
        },
        {
            "prompt_index": 1,
            "echo": {"prompt_index": 1, "type": "text", "text": "dog", "num_boxes": 0},
            "predictions": [
                {
                    "masks": [[[316, 561], [316, 562]], [[345, 251], [344, 252]]],
                    "confidence": 0.89453125,
                    "format": "polygon",
                }
            ],
        },
    ],
    "time": 0.14756996370851994,
}
HOSTED_SAM3_DICT = {
    "prompt_results": [
        {
            "prompt_index": 0,
            "echo": {
                "prompt_index": 0,
                "type": "text",
                "text": "bottle",
                "num_boxes": 0,
            },
            "predictions": [
                {
                    "masks": [[[1364, 200], [1365, 201]]],
                    "confidence": 0.8984375,
                    "format": "polygon",
                },
                {
                    "masks": [[[1140, 171], [1139, 170]]],
                    "confidence": 0.94140625,
                    "format": "polygon",
                },
            ],
        }
    ],
    "time": 0.7277156260097399,
}
SERVERLESS_SAM3_PVS_DICT = {
    "predictions": [
        {
            "masks": [
                [[713, 1276], [713, 1279], [714, 1279], [714, 1277]],
                [[711, 1273]],
                [[671, 1231], [671, 1234]],
                [[523, 1222], [522, 1223]],
            ],
            "confidence": 0.0025782063603401184,
            "format": "polygon",
        }
    ],
    "time": 0.07825545498053543,
}


@pytest.mark.parametrize(
    ("sam_result", "expected_xyxy", "expected_mask_shape"),
    [
        (
            [
                {
                    "segmentation": np.ones((10, 10), dtype=bool),
                    "bbox": [0, 0, 10, 10],
                    "area": 100,
                }
            ],
            np.array([[0, 0, 10, 10]], dtype=np.float32),
            (1, 10, 10),
        ),
        ([], np.empty((0, 4), dtype=np.float32), None),
    ],
)
def test_from_sam(
    sam_result: list[dict],
    expected_xyxy: np.ndarray,
    expected_mask_shape: tuple[int, ...] | None,
) -> None:
    detections = Detections.from_sam(sam_result=sam_result)

    assert np.array_equal(detections.xyxy, expected_xyxy)
    if expected_mask_shape is not None:
        assert detections.mask.shape == expected_mask_shape
    else:
        assert detections.mask is None


def test_from_sam_decodes_coco_rle_masks() -> None:
    """COCO RLE SAM outputs are decoded to dense boolean masks."""
    small_mask = np.zeros((4, 4), dtype=bool)
    small_mask[3, 3] = True
    large_mask = np.zeros((4, 4), dtype=bool)
    large_mask[:2, :2] = True
    sam_result = [
        {
            "segmentation": {
                "size": [4, 4],
                "counts": mask_to_rle(small_mask, compressed=True),
            },
            "bbox": [3, 3, 1, 1],
            "area": 1,
        },
        {
            "segmentation": {
                "size": [4, 4],
                "counts": mask_to_rle(large_mask, compressed=True),
            },
            "bbox": [0, 0, 2, 2],
            "area": 4,
        },
    ]

    detections = Detections.from_sam(sam_result=sam_result)

    assert len(detections) == 2
    assert isinstance(detections.mask, np.ndarray)
    assert detections.mask.dtype == bool
    assert detections.mask.shape == (2, 4, 4)
    np.testing.assert_array_equal(detections.mask, np.stack([large_mask, small_mask]))
    np.testing.assert_array_equal(
        detections.xyxy,
        np.array([[0, 0, 2, 2], [3, 3, 4, 4]], dtype=np.float32),
    )


@pytest.mark.parametrize(
    (
        "sam3_result",
        "resolution_wh",
        "expected_xyxy",
        "expected_confidence",
        "expected_class_id",
    ),
    [
        (
            {
                "prompt_results": [
                    {
                        "predictions": [
                            {
                                "format": "polygon",
                                "masks": [[[0, 0], [10, 0], [10, 10], [0, 10]]],
                                "confidence": 0.9,
                            }
                        ],
                        "prompt_index": 0,
                    }
                ]
            },
            (100, 100),
            np.array([[0, 0, 10, 10]], dtype=np.float32),
            np.array([0.9], dtype=np.float32),
            np.array([0], dtype=int),
        ),
        (
            {"prompt_results": []},
            (100, 100),
            np.empty((0, 4), dtype=np.float32),
            np.empty((0,), dtype=np.float32),
            np.empty((0,), dtype=int),
        ),
        (
            SERVERLESS_SAM3_DICT,
            (1000, 1000),
            np.array(
                [[294.0, 617.0, 496.0, 676.0], [316.0, 251.0, 345.0, 562.0]],
                dtype=np.float32,
            ),
            np.array([0.94921875, 0.89453125], dtype=np.float32),
            np.array([0, 1], dtype=int),
        ),
        (
            HOSTED_SAM3_DICT,
            (2000, 2000),
            np.array(
                [[1364.0, 200.0, 1365.0, 201.0], [1139.0, 170.0, 1140.0, 171.0]],
                dtype=np.float32,
            ),
            np.array([0.898438, 0.941406], dtype=np.float32),
            np.array([0, 0], dtype=int),
        ),
        (
            SERVERLESS_SAM3_PVS_DICT,
            (2000, 2000),
            np.array([[522.0, 1222.0, 714.0, 1279.0]], dtype=np.float32),
            np.array([0.00257821], dtype=np.float32),
            np.array([0], dtype=int),
        ),
    ],
)
def test_from_sam3(
    sam3_result: dict,
    resolution_wh: tuple[int, int],
    expected_xyxy: np.ndarray,
    expected_confidence: np.ndarray,
    expected_class_id: np.ndarray,
) -> None:
    detections = Detections.from_sam3(
        sam3_result=sam3_result, resolution_wh=resolution_wh
    )

    np.testing.assert_allclose(detections.xyxy, expected_xyxy, atol=1e-5)
    np.testing.assert_allclose(detections.confidence, expected_confidence, atol=1e-5)
    np.testing.assert_array_equal(detections.class_id, expected_class_id)


def test_from_sam3_invalid_resolution() -> None:
    sam3_result = {"prompt_results": []}
    with pytest.raises(
        ValueError, match=r"Both dimensions in resolution must be positive\."
    ):
        Detections.from_sam3(sam3_result=sam3_result, resolution_wh=(-100, 100))
