# ------------------------------------------------------------------------
# RF-DETR
# Copyright (c) 2025 Roboflow. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
# ------------------------------------------------------------------------
"""Regression tests for keypoint config defaults and namespace forwarding."""

import pytest

from rfdetr._namespace import _namespace_from_configs
from rfdetr.config import (
    KeypointTrainConfig,
    RFDETRBaseConfig,
    RFDETRKeypointPreviewConfig,
    SegmentationTrainConfig,
)


def test_keypoint_config_defaults() -> None:
    """Default model/train keypoint configuration values should match the preview contract."""
    model = RFDETRKeypointPreviewConfig()
    train = KeypointTrainConfig(dataset_dir="/tmp")

    assert model.use_grouppose_keypoints is True
    assert model.dual_projector is True
    assert model.dual_projector_kp_only is True
    assert model.num_keypoints_per_class == [17]
    assert model.positional_encoding_size == 576 // 12

    assert train.keypoint_l1_loss_coef == pytest.approx(1.0)
    assert train.keypoint_findable_loss_coef == pytest.approx(1.0)
    assert train.keypoint_visible_loss_coef == pytest.approx(1.0)
    assert train.keypoint_nll_loss_coef == pytest.approx(1.0)
    assert train.cls_loss_coef == pytest.approx(2.0)


def test_keypoint_preview_config_person_schema() -> None:
    """Person-keypoint preview config must expose a person-only schema."""
    model = RFDETRKeypointPreviewConfig()

    assert model.num_keypoints_per_class == [17]
    assert sum(model.num_keypoints_per_class) == 17
    assert model.out_feature_indexes == [3, 6, 9, 12]
    assert model.num_windows == 2
    assert model.dec_layers == 4
    assert model.patch_size == 12
    assert model.resolution == 576
    assert model.pretrain_weights == "rf-detr-keypoint-preview-xlarge.pth"


def test_keypoint_fields_propagate_to_namespace(tmp_path) -> None:
    """All keypoint config fields are forwarded through _namespace_from_configs."""
    model = RFDETRKeypointPreviewConfig()
    train = KeypointTrainConfig(
        dataset_dir=str(tmp_path),
        keypoint_flip_pairs=[0, 1, 2, 3],
        keypoint_l1_loss_coef=1.5,
        keypoint_findable_loss_coef=2.5,
        keypoint_visible_loss_coef=3.5,
        keypoint_nll_loss_coef=4.5,
    )

    namespace = _namespace_from_configs(model, train)

    assert namespace.use_grouppose_keypoints is True
    assert namespace.keypoint_cross_attn is True
    assert namespace.inter_instance_kp_attn is False
    assert namespace.grouppose_keypoint_dim_downscale == 1
    assert namespace.dual_projector is True
    assert namespace.dual_projector_kp_only is True
    assert namespace.num_keypoints_per_class == [17]
    assert namespace.keypoint_flip_pairs == [0, 1, 2, 3]
    assert namespace.keypoint_l1_loss_coef == pytest.approx(1.5)
    assert namespace.keypoint_findable_loss_coef == pytest.approx(2.5)
    assert namespace.keypoint_visible_loss_coef == pytest.approx(3.5)
    assert namespace.keypoint_nll_loss_coef == pytest.approx(4.5)


def test_keypoint_nll_loss_coef_default_restored_to_1_0() -> None:
    """keypoint_nll_loss_coef must default to 1.0 after the 0.5 revert.

    The 0.5 default was introduced to dampen OKS@75 oscillation.  It was later reverted to 1.0 to align with all other
    keypoint loss terms (l1, findable, visible).  This test guards against silent regressions.
    """
    train = KeypointTrainConfig(dataset_dir="/tmp")
    assert train.keypoint_nll_loss_coef == pytest.approx(1.0)


def test_segmentation_train_config_cls_loss_coef_default() -> None:
    """SegmentationTrainConfig.cls_loss_coef must default to 1.0, not the erroneous 5.0.

    The 5.0 value was always present in SegmentationTrainConfig but was dead code pre-v1.7 (namespace builder read from
    ModelConfig=1.0).  The v1.7 TrainConfig ownership migration silently activated it.  This test guards against re-
    introducing that regression.
    """
    tc = SegmentationTrainConfig(dataset_dir="/tmp")
    assert tc.cls_loss_coef == pytest.approx(1.0)


def test_unknown_keypoint_fields_are_not_public_config_fields() -> None:
    """Private keypoint implementation fields are not accepted as public model config."""
    with pytest.raises(ValueError, match="Unknown parameter"):
        RFDETRBaseConfig(num_classes=1, keypoint_private_hidden_dim=256)

    # KeypointTrainConfig (a TrainConfig subclass) uses extra="forbid", so unknown
    # kwargs raise with a helpful message rather than being silently dropped.
    with pytest.raises(ValueError, match="Unknown parameter"):
        KeypointTrainConfig(
            dataset_dir="/tmp",
            keypoint_private_hidden_dim=256,
            keypoint_private_loss_coef=1.0,
        )
