# ------------------------------------------------------------------------
# RF-DETR
# Copyright (c) 2025 Roboflow. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
# ------------------------------------------------------------------------
"""Tests for keypoint matching costs in HungarianMatcher."""

import torch

from rfdetr.models.matcher import HungarianMatcher


def _base_outputs(num_queries: int = 2) -> dict[str, torch.Tensor]:
    """Build minimal detection outputs used across matcher keypoint tests.

    Examples:
        >>> outputs = _base_outputs(num_queries=3)
        >>> outputs["pred_logits"].shape
        torch.Size([1, 3, 1])
    """
    pred_logits = torch.full((1, num_queries, 1), 5.0, dtype=torch.float32)
    pred_boxes = torch.tensor([0.5, 0.5, 0.2, 0.2], dtype=torch.float32).view(1, 1, 4).repeat(1, num_queries, 1)
    return {
        "pred_logits": pred_logits,
        "pred_boxes": pred_boxes,
    }


def test_matcher_keypoint_cost_list_of_dicts_targets() -> None:
    """Keypoint matching costs should work with public list-of-dicts targets."""
    matcher = HungarianMatcher(
        cost_class=0.0,
        cost_bbox=1.0,
        cost_giou=0.0,
        num_keypoints_per_class=[1],
        keypoint_l1_loss_coef=10.0,
        keypoint_findable_loss_coef=0.0,
        keypoint_visible_loss_coef=0.0,
        keypoint_nll_loss_coef=0.0,
    )
    outputs = _base_outputs()
    outputs["pred_keypoints"] = torch.zeros((1, 2, 1, 8), dtype=torch.float32)
    outputs["pred_keypoints"][0, 0, 0, :2] = torch.tensor([0.5, 0.5], dtype=torch.float32)
    outputs["pred_keypoints"][0, 1, 0, :2] = torch.tensor([0.0, 0.0], dtype=torch.float32)
    targets = [
        {
            "labels": torch.tensor([0], dtype=torch.int64),
            "boxes": torch.tensor([[0.5, 0.5, 0.2, 0.2]], dtype=torch.float32),
            "keypoints": torch.tensor([[[0.5, 0.5, 2.0]]], dtype=torch.float32),
        }
    ]

    matched_queries, matched_targets = matcher(outputs, targets)[0]

    assert matched_queries.tolist() == [0]
    assert matched_targets.tolist() == [0]


def test_matcher_keypoint_cost_coefficients_off() -> None:
    """Zero keypoint coefficients should preserve non-keypoint matching behavior."""
    base_matcher = HungarianMatcher(cost_class=1.0, cost_bbox=1.0, cost_giou=1.0)
    keypoint_matcher = HungarianMatcher(
        cost_class=1.0,
        cost_bbox=1.0,
        cost_giou=1.0,
        num_keypoints_per_class=[1],
        keypoint_l1_loss_coef=0.0,
        keypoint_findable_loss_coef=0.0,
        keypoint_visible_loss_coef=0.0,
        keypoint_nll_loss_coef=0.0,
    )
    outputs = _base_outputs()
    outputs["pred_logits"][0, 0, 0] = 10.0
    outputs["pred_logits"][0, 1, 0] = -10.0
    outputs["pred_boxes"][0, 1, :] = torch.tensor([0.1, 0.1, 0.1, 0.1], dtype=torch.float32)
    targets = [
        {
            "labels": torch.tensor([0], dtype=torch.int64),
            "boxes": torch.tensor([[0.5, 0.5, 0.2, 0.2]], dtype=torch.float32),
            "keypoints": torch.tensor([[[0.0, 0.0, 2.0]]], dtype=torch.float32),
        }
    ]
    outputs_with_keypoints = dict(outputs)
    outputs_with_keypoints["pred_keypoints"] = torch.zeros((1, 2, 1, 8), dtype=torch.float32)

    base_indices = base_matcher(outputs, targets)[0]
    keypoint_indices = keypoint_matcher(outputs_with_keypoints, targets)[0]

    assert base_indices[0].tolist() == keypoint_indices[0].tolist()
    assert base_indices[1].tolist() == keypoint_indices[1].tolist()


def test_matcher_keypoint_empty_targets() -> None:
    """Empty keypoint targets should return valid empty match results."""
    matcher = HungarianMatcher(
        cost_class=1.0,
        cost_bbox=1.0,
        cost_giou=1.0,
        num_keypoints_per_class=[1],
        keypoint_l1_loss_coef=1.0,
        keypoint_findable_loss_coef=1.0,
        keypoint_visible_loss_coef=1.0,
        keypoint_nll_loss_coef=1.0,
    )
    outputs = _base_outputs(num_queries=3)
    outputs["pred_keypoints"] = torch.zeros((1, 3, 1, 8), dtype=torch.float32)
    targets = [
        {
            "labels": torch.zeros((0,), dtype=torch.int64),
            "boxes": torch.zeros((0, 4), dtype=torch.float32),
            "keypoints": torch.zeros((0, 1, 3), dtype=torch.float32),
        }
    ]

    matched_queries, matched_targets = matcher(outputs, targets)[0]

    assert matched_queries.numel() == 0
    assert matched_targets.numel() == 0
