# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0

from pathlib import Path
import sys
import types

# Stub structlog so this test runs where the optional dep is absent.
if "structlog" not in sys.modules:

    class _DummyLogger:
        def __getattr__(self, _name):
            return lambda *args, **kwargs: None

    sys.modules["structlog"] = types.SimpleNamespace(
        BoundLogger = _DummyLogger,
        get_logger = lambda *args, **kwargs: _DummyLogger(),
    )

from utils.paths.path_utils import (
    resolve_cached_repo_id_case,
    get_cache_case_resolution_stats,
    reset_cache_case_resolution_state,
)
import utils.paths.path_utils as path_utils


def _mk_cache_repo(cache_root: Path, repo_id: str) -> Path:
    repo_dir = cache_root / f"models--{repo_id.replace('/', '--')}"
    repo_dir.mkdir(parents = True, exist_ok = True)
    return repo_dir


def test_resolve_cached_repo_id_case_exact_hit(tmp_path, monkeypatch):
    reset_cache_case_resolution_state()
    _mk_cache_repo(tmp_path, "Org/Model")
    monkeypatch.setattr(path_utils, "_hf_hub_cache_dir", lambda: tmp_path)

    resolved = resolve_cached_repo_id_case("Org/Model")

    assert resolved == "Org/Model"
    stats = get_cache_case_resolution_stats()
    assert stats["calls"] == 1
    assert stats["exact_hits"] == 1
    assert stats["variant_hits"] == 0


def test_resolve_cached_repo_id_case_variant_hit(tmp_path, monkeypatch):
    reset_cache_case_resolution_state()
    _mk_cache_repo(tmp_path, "Org/Model")
    monkeypatch.setattr(path_utils, "_hf_hub_cache_dir", lambda: tmp_path)

    resolved = resolve_cached_repo_id_case("org/model")

    assert resolved == "Org/Model"
    stats = get_cache_case_resolution_stats()
    assert stats["variant_hits"] == 1
    assert stats["tie_breaks"] == 0


def test_resolve_cached_repo_id_case_tie_break_deterministic(tmp_path, monkeypatch):
    reset_cache_case_resolution_state()
    _mk_cache_repo(tmp_path, "Org/Model")
    _mk_cache_repo(tmp_path, "org/model")
    monkeypatch.setattr(path_utils, "_hf_hub_cache_dir", lambda: tmp_path)

    resolved = resolve_cached_repo_id_case("oRg/mOdEl")

    # Deterministic rule: lexical sort of candidate repo ids.
    assert resolved == "Org/Model"
    stats = get_cache_case_resolution_stats()
    assert stats["variant_hits"] == 1
    assert stats["tie_breaks"] == 1


def test_resolve_cached_repo_id_case_no_cache_fallback(tmp_path, monkeypatch):
    reset_cache_case_resolution_state()
    monkeypatch.setattr(path_utils, "_hf_hub_cache_dir", lambda: tmp_path)

    resolved = resolve_cached_repo_id_case("Org/Missing")

    assert resolved == "Org/Missing"
    stats = get_cache_case_resolution_stats()
    assert stats["fallbacks"] == 1
    assert stats["variant_hits"] == 0
    assert stats["exact_hits"] == 0


def test_resolve_cached_repo_id_case_memoization(tmp_path, monkeypatch):
    reset_cache_case_resolution_state()
    _mk_cache_repo(tmp_path, "Org/Model")
    monkeypatch.setattr(path_utils, "_hf_hub_cache_dir", lambda: tmp_path)

    first = resolve_cached_repo_id_case("org/model")
    second = resolve_cached_repo_id_case("org/model")

    assert first == "Org/Model"
    assert second == "Org/Model"
    stats = get_cache_case_resolution_stats()
    assert stats["calls"] == 2
    assert stats["variant_hits"] == 1
    assert stats["memo_hits"] == 1


def test_resolve_cached_repo_id_case_late_cache_population(tmp_path, monkeypatch):
    """Regression guard: memoized fallback should not hide a later cache variant."""
    reset_cache_case_resolution_state()
    monkeypatch.setattr(path_utils, "_hf_hub_cache_dir", lambda: tmp_path)

    first = resolve_cached_repo_id_case("org/model")
    assert first == "org/model"

    # Cache populated after first miss (e.g. another code path/download).
    _mk_cache_repo(tmp_path, "Org/Model")

    second = resolve_cached_repo_id_case("org/model")

    # Second lookup should pick up the now-existing variant.
    assert second == "Org/Model"
