"""`Result.to_supervision()` — conversion to `supervision.Detections`.

Conversion, not inheritance. `Result` is acaua's native type; the supervision
object is a conversion artifact so downstream users can plug into Roboflow
Supervision's annotation + tracker ecosystem.
"""

from __future__ import annotations

import numpy as np
import pytest
import supervision as sv
import torch

from acaua import DetectionResult, SegmentationResult


class TestDetectionResult:
    def test_non_empty_detection(self) -> None:
        det = DetectionResult(
            boxes=torch.tensor([[0.0, 0.0, 10.0, 10.0], [5.0, 5.0, 20.0, 20.0]]),
            labels=torch.tensor([3, 7], dtype=torch.int64),
            scores=torch.tensor([0.9, 0.8]),
        )
        out = det.to_supervision()
        assert isinstance(out, sv.Detections)
        assert out.xyxy.shape == (2, 4)
        assert out.class_id is not None
        np.testing.assert_array_equal(out.class_id, np.array([3, 7]))
        assert out.confidence is not None
        np.testing.assert_allclose(out.confidence, np.array([0.9, 0.8]), rtol=1e-5)

    def test_empty_detection(self) -> None:
        det = DetectionResult(
            boxes=torch.zeros(0, 4),
            labels=torch.zeros(0, dtype=torch.int64),
            scores=torch.zeros(0),
        )
        out = det.to_supervision()
        assert isinstance(out, sv.Detections)
        assert len(out) == 0

    def test_detection_has_no_mask(self) -> None:
        det = DetectionResult(
            boxes=torch.zeros(1, 4),
            labels=torch.zeros(1, dtype=torch.int64),
            scores=torch.zeros(1),
        )
        out = det.to_supervision()
        assert out.mask is None


class TestSegmentationResult:
    def test_non_empty_segmentation_has_mask(self) -> None:
        seg = SegmentationResult(
            boxes=torch.tensor([[0.0, 0.0, 8.0, 8.0]]),
            labels=torch.tensor([1], dtype=torch.int64),
            scores=torch.tensor([0.7]),
            masks=torch.ones(1, 16, 16, dtype=torch.bool),
        )
        out = seg.to_supervision()
        assert isinstance(out, sv.Detections)
        assert out.mask is not None
        assert out.mask.shape == (1, 16, 16)
        assert out.mask.dtype == bool

    def test_empty_segmentation(self) -> None:
        seg = SegmentationResult(
            boxes=torch.zeros(0, 4),
            labels=torch.zeros(0, dtype=torch.int64),
            scores=torch.zeros(0),
            masks=torch.zeros(0, 16, 16, dtype=torch.bool),
        )
        out = seg.to_supervision()
        assert len(out) == 0


class TestDeviceAgnostic:
    """Results produced on non-CPU tensors must still convert cleanly."""

    def test_detection_on_detached_tensors_does_not_require_grad(self) -> None:
        # .detach().cpu() is used under the hood; make sure grad-requiring
        # tensors survive the conversion.
        det = DetectionResult(
            boxes=torch.zeros(1, 4, requires_grad=True),
            labels=torch.zeros(1, dtype=torch.int64),
            scores=torch.zeros(1, requires_grad=True),
        )
        out = det.to_supervision()
        assert isinstance(out, sv.Detections)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason="no CUDA")
    def test_detection_on_cuda(self) -> None:
        det = DetectionResult(
            boxes=torch.zeros(1, 4).cuda(),
            labels=torch.zeros(1, dtype=torch.int64).cuda(),
            scores=torch.zeros(1).cuda(),
        )
        out = det.to_supervision()
        assert out.xyxy.shape == (1, 4)
