"""Shape contract on `Result` / `DetectionResult` / `SegmentationResult`.

Every task-specific subclass must expose the common `boxes / labels / scores`
fields on the base, be frozen (immutable), use slots (no `__dict__`), and
preserve subclassing via inheritance.
"""

from __future__ import annotations

from dataclasses import FrozenInstanceError, fields

import pytest
import torch

from acaua import DetectionResult, Result, SegmentationResult


def _detection() -> DetectionResult:
    return DetectionResult(
        boxes=torch.zeros(2, 4),
        labels=torch.zeros(2, dtype=torch.int64),
        scores=torch.zeros(2),
    )


def _segmentation() -> SegmentationResult:
    return SegmentationResult(
        boxes=torch.zeros(2, 4),
        labels=torch.zeros(2, dtype=torch.int64),
        scores=torch.zeros(2),
        masks=torch.zeros(2, 16, 16, dtype=torch.bool),
    )


class TestBaseFields:
    def test_base_fields_shared(self) -> None:
        det = _detection()
        seg = _segmentation()
        for instance in (det, seg):
            assert hasattr(instance, "boxes")
            assert hasattr(instance, "labels")
            assert hasattr(instance, "scores")

    def test_segmentation_adds_masks(self) -> None:
        seg = _segmentation()
        assert hasattr(seg, "masks")
        assert seg.masks.shape == (2, 16, 16)

    def test_detection_does_not_have_masks(self) -> None:
        det = _detection()
        assert not hasattr(det, "masks")


class TestSubclassHierarchy:
    def test_detection_is_result(self) -> None:
        assert isinstance(_detection(), Result)

    def test_segmentation_is_result(self) -> None:
        assert isinstance(_segmentation(), Result)

    def test_subclasses_are_distinct(self) -> None:
        assert not isinstance(_detection(), SegmentationResult)
        assert not isinstance(_segmentation(), DetectionResult)


class TestImmutability:
    def test_detection_is_frozen(self) -> None:
        det = _detection()
        with pytest.raises(FrozenInstanceError):
            det.boxes = torch.zeros(3, 4)  # type: ignore[misc]

    def test_segmentation_is_frozen(self) -> None:
        seg = _segmentation()
        with pytest.raises(FrozenInstanceError):
            seg.masks = torch.zeros(3, 8, 8, dtype=torch.bool)  # type: ignore[misc]


class TestSlots:
    def test_no_dict(self) -> None:
        # slots=True prevents __dict__ from being created per instance.
        det = _detection()
        assert not hasattr(det, "__dict__")
        seg = _segmentation()
        assert not hasattr(seg, "__dict__")

    def test_cannot_add_new_attribute(self) -> None:
        det = _detection()
        # `slots=True + frozen=True` raises one of several exceptions depending
        # on the Python/dataclass version interaction. All three are acceptable
        # as long as the assignment fails.
        with pytest.raises((AttributeError, FrozenInstanceError, TypeError)):
            det.garbage = 1  # type: ignore[attr-defined]


class TestDataclassShape:
    def test_base_has_three_fields(self) -> None:
        names = {f.name for f in fields(Result)}
        assert names == {"boxes", "labels", "scores"}

    def test_segmentation_has_four_fields(self) -> None:
        names = {f.name for f in fields(SegmentationResult)}
        assert names == {"boxes", "labels", "scores", "masks"}
