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

from types import SimpleNamespace

import pytest
import torch

from rfdetr.utilities.state_dict import strip_checkpoint


class TestStripCheckpoint:
    def test_strip_checkpoint_keeps_only_model_and_args(self, tmp_path):
        checkpoint_path = tmp_path / "checkpoint_best_total.pth"
        torch.save(
            {
                "model": {"weight": torch.tensor([1.0])},
                "args": SimpleNamespace(class_names=["a"]),
                "optimizer": {"lr": 1e-4},
            },
            checkpoint_path,
        )

        strip_checkpoint(str(checkpoint_path))

        stripped = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
        assert set(stripped.keys()) == {"model", "args"}

    def test_strip_checkpoint_preserves_model_name_when_present(self, tmp_path):
        checkpoint_path = tmp_path / "checkpoint_best_total.pth"
        torch.save(
            {
                "model": {"weight": torch.tensor([1.0])},
                "args": SimpleNamespace(class_names=["a"]),
                "model_name": "RFDETRSmall",
                "optimizer": {"lr": 1e-4},
            },
            checkpoint_path,
        )

        strip_checkpoint(str(checkpoint_path))

        stripped = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
        assert set(stripped.keys()) == {"model", "args", "model_name"}
        assert stripped["model_name"] == "RFDETRSmall"

    def test_strip_checkpoint_omits_model_name_when_absent(self, tmp_path):
        checkpoint_path = tmp_path / "checkpoint_best_total.pth"
        torch.save(
            {
                "model": {"weight": torch.tensor([1.0])},
                "args": SimpleNamespace(class_names=["a"]),
                "optimizer": {"lr": 1e-4},
            },
            checkpoint_path,
        )

        strip_checkpoint(str(checkpoint_path))

        stripped = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
        assert "model_name" not in stripped

    def test_strip_checkpoint_is_atomic_when_save_fails(self, tmp_path, monkeypatch):
        checkpoint_path = tmp_path / "checkpoint_best_total.pth"
        original_checkpoint = {
            "model": {"weight": torch.tensor([1.0])},
            "args": SimpleNamespace(class_names=["a"]),
            "optimizer": {"lr": 1e-4},
        }
        torch.save(original_checkpoint, checkpoint_path)

        original_torch_save = torch.save

        def failing_torch_save(obj, destination, *args, **kwargs):
            if str(destination) != str(checkpoint_path):
                raise RuntimeError("simulated save failure")
            return original_torch_save(obj, destination, *args, **kwargs)

        monkeypatch.setattr(torch, "save", failing_torch_save)

        with pytest.raises(RuntimeError, match="simulated save failure"):
            strip_checkpoint(str(checkpoint_path))

        recovered = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
        assert set(recovered.keys()) == set(original_checkpoint.keys())
        assert recovered["model"]["weight"].equal(original_checkpoint["model"]["weight"])
        assert recovered["optimizer"] == original_checkpoint["optimizer"]
