# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========

import json
import os
from pathlib import Path

import pytest

from mirage.resource import registry
from mirage.resource.hf_buckets import HfBucketsResource
from mirage.resource.registry import (REGISTRY, build_resource,
                                      known_resources, register_resource)

SPEC_RESOURCES = (Path(__file__).resolve().parents[3] / "spec" / "python" /
                  "resources.json")


def test_registry_matches_the_committed_spec_manifest():
    """REGISTRY must equal the committed dump the parity gate reads.

    The expected set used to be hand-copied into this file, so the
    assertion could only ever restate whatever the registry already said —
    it pinned SharePoint's absence rather than catching it. The manifest is
    regenerated by scripts/gen_specs.py and diffed against TypeScript by
    scripts/check_spec_parity.py, so a name added on one side and not the
    other now fails a gate instead of a hand edit.
    """
    manifest = json.loads(SPEC_RESOURCES.read_text())
    assert set(REGISTRY) == set(manifest["registry"]), (
        "REGISTRY drifted from spec/python/resources.json; "
        "rerun scripts/gen_specs.py")


def test_capabilities_match_the_committed_spec_manifest():
    """Every registry class's capability values must match the dump.

    Membership alone says a backend can be built, not how it behaves once
    mounted. These four values decide how stale a listing may be, whether
    reads are cached, whether a snapshot records a fingerprint and whether
    a size is knowable without fetching, and each was an independent hand
    edit until the dump started carrying them.
    """
    manifest = json.loads(SPEC_RESOURCES.read_text())["capabilities"]
    live = {
        name: {
            "index_ttl": cls.index_ttl,
            "caches_reads": cls.caches_reads,
            "supports_snapshot": cls.SUPPORTS_SNAPSHOT,
            "sizes_always_known": cls.SIZES_ALWAYS_KNOWN,
        }
        for name, cls in ((n, registry.resolve_class(e.resource_path))
                          for n, e in REGISTRY.items())
    }
    dumped = {
        name: {
            k: v
            for k, v in entry.items() if k in live[name]
        }
        for name, entry in manifest.items()
    }
    assert live == dumped, ("resource capabilities drifted from "
                            "spec/python/resources.json; rerun "
                            "scripts/gen_specs.py")


def test_build_ram_returns_ram_resource():
    from mirage.resource.ram import RAMResource
    p = build_resource("ram")
    assert isinstance(p, RAMResource)


def test_build_disk_takes_raw_kwargs(tmp_path):
    from mirage.resource.disk import DiskResource
    p = build_resource("disk", {"root": str(tmp_path)})
    assert isinstance(p, DiskResource)


def test_build_s3_uses_config_class():
    from mirage.resource.s3 import S3Resource
    p = build_resource(
        "s3", {
            "bucket": "b",
            "region": "us-east-1",
            "aws_access_key_id": "k",
            "aws_secret_access_key": "s",
        })
    assert isinstance(p, S3Resource)
    assert p.config.bucket == "b"
    assert p.config.region == "us-east-1"


def test_build_r2_uses_r2_config():
    from mirage.resource.r2 import R2Resource
    p = build_resource(
        "r2", {
            "bucket": "b",
            "account_id": "acct",
            "access_key_id": "k",
            "secret_access_key": "s",
        })
    assert isinstance(p, R2Resource)


@pytest.mark.skipif(not os.environ.get("REDIS_URL"),
                    reason="REDIS_URL not set")
def test_build_redis_takes_raw_kwargs():
    from mirage.resource.redis import RedisResource
    p = build_resource("redis", {
        "url": os.environ["REDIS_URL"],
        "key_prefix": "test:",
    })
    assert isinstance(p, RedisResource)


def test_unknown_resource_raises_keyerror():
    with pytest.raises(KeyError, match="unknown resource 'nonsense'"):
        build_resource("nonsense")


def test_registry_module_import_is_free_of_resource_deps():
    import importlib
    import sys
    if "mirage.resource.registry" in sys.modules:
        del sys.modules["mirage.resource.registry"]
    importlib.import_module("mirage.resource.registry")


def test_build_hf_buckets_resource():
    r = build_resource("hf_buckets", {"bucket": "o/b"})
    assert isinstance(r, HfBucketsResource)


class FakeCustomConfig:

    def __init__(self, url: str = "") -> None:
        self.url = url


class FakeCustomResource:

    def __init__(self, config: FakeCustomConfig) -> None:
        self.config = config


class FakeKwargsResource:

    def __init__(self, root: str = "/") -> None:
        self.root = root


class FakeConfigClsResource:

    CONFIG_CLS = FakeCustomConfig

    def __init__(self, config: FakeCustomConfig) -> None:
        self.config = config


@pytest.fixture
def clean_registry(monkeypatch):
    monkeypatch.setattr(registry, "_CUSTOM", {})
    monkeypatch.setattr(registry, "_entry_points_loaded", False)


def test_register_resource_class_and_config(clean_registry):
    register_resource("fake_custom", FakeCustomResource, FakeCustomConfig)
    built = build_resource("fake_custom", {"url": "http://x"})
    assert isinstance(built, FakeCustomResource)
    assert built.config.url == "http://x"


def test_register_resource_kwargs_config(clean_registry):
    register_resource("fake_kwargs", FakeKwargsResource)
    built = build_resource("fake_kwargs", {"root": "/data"})
    assert isinstance(built, FakeKwargsResource)
    assert built.root == "/data"


def test_register_resource_config_cls_attribute(clean_registry):
    register_resource("fake_attr", FakeConfigClsResource)
    built = build_resource("fake_attr", {"url": "http://y"})
    assert built.config.url == "http://y"


def test_register_resource_rejects_builtin_shadow(clean_registry):
    with pytest.raises(ValueError):
        register_resource("s3", FakeCustomResource)


def test_register_resource_spec_string(clean_registry):
    register_resource("fake_spec",
                      "tests.resource.test_registry:FakeKwargsResource")
    built = build_resource("fake_spec", {"root": "/spec"})
    assert built.root == "/spec"


def test_known_resources_includes_custom(clean_registry):
    register_resource("fake_custom", FakeCustomResource, FakeCustomConfig)
    names = known_resources()
    assert "fake_custom" in names
    assert "s3" in names


def test_entry_point_discovery(clean_registry, monkeypatch):
    import importlib.metadata

    ep = importlib.metadata.EntryPoint(
        name="fake_ep",
        value="tests.resource.test_registry:FakeKwargsResource",
        group="mirage.resources",
    )

    def fake_entry_points(*, group):
        assert group == "mirage.resources"
        return [ep]

    monkeypatch.setattr(importlib.metadata, "entry_points", fake_entry_points)
    built = build_resource("fake_ep", {"root": "/ep"})
    assert built.root == "/ep"
    assert "fake_ep" in known_resources()


def test_entry_point_does_not_shadow_registered(clean_registry, monkeypatch):
    import importlib.metadata

    ep = importlib.metadata.EntryPoint(
        name="fake_custom",
        value="tests.resource.test_registry:FakeKwargsResource",
        group="mirage.resources",
    )
    monkeypatch.setattr(importlib.metadata, "entry_points",
                        lambda *, group: [ep])
    register_resource("fake_custom", FakeCustomResource, FakeCustomConfig)
    built = build_resource("fake_custom", {"url": "http://z"})
    assert isinstance(built, FakeCustomResource)


def test_unknown_resource_lists_known(clean_registry):
    with pytest.raises(KeyError, match="unknown resource"):
        build_resource("nope_not_real")
