"""Helper utilities for custom sandbox strategy Robot integration tests.

Covers end-to-end integration of:
- SandboxStrategyProtocol and runtime checking
- SandboxStrategyRegistry registration and lookup
- BuiltInSandboxStrategyAdapter full lifecycle
- CustomStrategyConfig validation
- SandboxFactory custom strategy integration

Based on issue #586.
"""

from __future__ import annotations

import os
import sys
import tempfile
from datetime import datetime

from cleveragents.domain.models.core.resource import (
    PhysVirt,
    Resource,
    ResourceCapabilities,
)
from cleveragents.domain.models.core.resource import (
    SandboxStrategy as SandboxStrategyEnum,
)
from cleveragents.domain.models.core.sandbox_strategy import (
    DiffEntry,
    DiffView,
    SandboxRef,
    SandboxStrategyProtocol,
)
from cleveragents.infrastructure.plugins.exceptions import (
    ProtocolMismatchError,
)
from cleveragents.infrastructure.sandbox.factory import SandboxFactory
from cleveragents.infrastructure.sandbox.strategy_adapter import (
    BuiltInSandboxStrategyAdapter,
)
from cleveragents.infrastructure.sandbox.strategy_registry import (
    CustomStrategyConfig,
    SandboxStrategyRegistry,
)

# ---------------------------------------------------------------------------
# ULID helper
# ---------------------------------------------------------------------------

_CTR = 0


def _ulid(name: str) -> str:
    global _CTR
    _CTR += 1
    base = abs(hash(name)) % (10**20)
    raw = f"{_CTR:06d}{base:020d}"
    charset = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
    result = ""
    for ch in raw:
        result += charset[int(ch)]
    return (result + "0" * 26)[:26]


def _make_resource(name: str, location: str) -> Resource:
    return Resource(
        resource_id=_ulid(name),
        name=name,
        resource_type_name="fs-directory",
        classification=PhysVirt.PHYSICAL,
        sandbox_strategy=SandboxStrategyEnum.NONE,
        location=location,
        capabilities=ResourceCapabilities(
            readable=True,
            writable=True,
            sandboxable=True,
            checkpointable=False,
        ),
    )


# ---------------------------------------------------------------------------
# Test: SandboxRef creation and immutability
# ---------------------------------------------------------------------------


def _sandbox_ref_lifecycle() -> None:
    """Integration test: SandboxRef creation and properties."""
    ref = SandboxRef(
        sandbox_id="ref-001",
        plan_id="plan-001",
        resource_id="res-001",
        created_at=datetime.now(),
        metadata={"backend": "test"},
    )
    assert ref.sandbox_id == "ref-001"
    assert ref.plan_id == "plan-001"
    assert ref.metadata["backend"] == "test"

    # Frozen check
    try:
        ref.sandbox_id = "mutated"  # type: ignore[misc]
        print("FAIL: Expected FrozenInstanceError")
        return
    except (AttributeError, Exception):
        pass

    print("sandbox-ref-lifecycle-ok")


# ---------------------------------------------------------------------------
# Test: DiffView and DiffEntry
# ---------------------------------------------------------------------------


def _diff_view_lifecycle() -> None:
    """Integration test: DiffView and DiffEntry models."""
    entry = DiffEntry(path="test.py", operation="added", after=b"content")
    assert entry.path == "test.py"
    assert entry.operation == "added"

    view = DiffView(
        sandbox_id="dv-001",
        entries=[entry],
        summary="1 file added",
    )
    assert len(view.entries) == 1
    assert view.sandbox_id == "dv-001"

    # Empty path should fail
    try:
        DiffEntry(path="", operation="modified")
        print("FAIL: Expected ValidationError")
        return
    except Exception:
        pass

    print("diff-view-lifecycle-ok")


# ---------------------------------------------------------------------------
# Test: SandboxStrategyProtocol runtime check
# ---------------------------------------------------------------------------


def _protocol_check() -> None:
    """Integration test: Protocol runtime checking."""

    class _Good:
        def create(self, plan_id, resource):
            return None

        def read(self, ref, path):
            return b""

        def write(self, ref, path, content):
            return None

        def diff(self, ref):
            return None

        def commit(self, ref):
            pass

        def rollback(self, ref):
            pass

        def checkpoint(self, ref, checkpoint_id):
            pass

        def restore_checkpoint(self, ref, checkpoint_id):
            pass

        def cleanup(self, ref):
            pass

    class _Bad:
        def create(self, plan_id, resource):
            return None

    good = _Good()
    bad = _Bad()
    assert isinstance(good, SandboxStrategyProtocol)
    assert not isinstance(bad, SandboxStrategyProtocol)

    print("protocol-check-ok")


# ---------------------------------------------------------------------------
# Test: SandboxStrategyRegistry
# ---------------------------------------------------------------------------


def _registry_lifecycle() -> None:
    """Integration test: Registry registration, lookup, config, clear."""
    registry = SandboxStrategyRegistry(allowed_prefixes=("cleveragents.",))

    # Register
    cls = registry.register(
        "adapter",
        "cleveragents.infrastructure.sandbox.strategy_adapter",
        "BuiltInSandboxStrategyAdapter",
    )
    assert cls is BuiltInSandboxStrategyAdapter
    assert registry.has("adapter")
    assert "adapter" in registry.list_strategies()

    # Get
    assert registry.get("adapter") is BuiltInSandboxStrategyAdapter
    assert registry.get("nonexistent") is None

    # Non-protocol rejection
    try:
        registry.register(
            "bad",
            "cleveragents.infrastructure.sandbox.protocol",
            "SandboxError",
        )
        print("FAIL: Expected ProtocolMismatchError")
        return
    except ProtocolMismatchError:
        pass

    # Config-driven registration
    configs = {
        "cfg1": {
            "module": "cleveragents.infrastructure.sandbox.strategy_adapter",
            "class": "BuiltInSandboxStrategyAdapter",
        },
    }
    registered = registry.register_all_from_config(configs)
    assert len(registered) == 1

    # Clear
    registry.clear()
    assert len(registry.list_strategies()) == 0

    print("registry-lifecycle-ok")


# ---------------------------------------------------------------------------
# Test: BuiltInSandboxStrategyAdapter full lifecycle
# ---------------------------------------------------------------------------


def _adapter_lifecycle() -> None:
    """Integration test: Adapter create/read/write/diff/commit/cleanup."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Seed a file
        with open(os.path.join(tmpdir, "seed.txt"), "w") as f:
            f.write("seed")

        adapter = BuiltInSandboxStrategyAdapter(strategy_name="copy_on_write")
        resource = Resource(
            resource_id=_ulid("adapter-res"),
            name="adapter-res",
            resource_type_name="fs-directory",
            classification=PhysVirt.PHYSICAL,
            sandbox_strategy=SandboxStrategyEnum.COPY_ON_WRITE,
            location=tmpdir,
            capabilities=ResourceCapabilities(
                readable=True,
                writable=True,
                sandboxable=True,
                checkpointable=False,
            ),
        )

        # Create
        ref = adapter.create("plan-adapter", resource)
        assert isinstance(ref, SandboxRef)
        assert ref.plan_id == "plan-adapter"

        # Write
        entry = adapter.write(ref, "new_file.txt", b"hello")
        assert isinstance(entry, DiffEntry)
        assert entry.operation == "added"

        # Read
        data = adapter.read(ref, "new_file.txt")
        assert data == b"hello"

        # Diff
        view = adapter.diff(ref)
        assert isinstance(view, DiffView)

        # Cleanup
        adapter.cleanup(ref)
        assert ref.sandbox_id not in adapter._sandboxes

    print("adapter-lifecycle-ok")


# ---------------------------------------------------------------------------
# Test: Adapter checkpoint/restore
# ---------------------------------------------------------------------------


def _adapter_checkpoint() -> None:
    """Integration test: Adapter checkpoint and restore."""
    with tempfile.TemporaryDirectory() as tmpdir:
        with open(os.path.join(tmpdir, "seed.txt"), "w") as f:
            f.write("seed")

        adapter = BuiltInSandboxStrategyAdapter(strategy_name="copy_on_write")
        resource = Resource(
            resource_id=_ulid("cp-res"),
            name="cp-res",
            resource_type_name="fs-directory",
            classification=PhysVirt.PHYSICAL,
            sandbox_strategy=SandboxStrategyEnum.COPY_ON_WRITE,
            location=tmpdir,
            capabilities=ResourceCapabilities(
                readable=True,
                writable=True,
                sandboxable=True,
                checkpointable=False,
            ),
        )

        ref = adapter.create("plan-cp", resource)
        adapter.write(ref, "cp.txt", b"v1")
        adapter.checkpoint(ref, "cp-1")
        adapter.write(ref, "cp.txt", b"v2")

        # Verify v2
        assert adapter.read(ref, "cp.txt") == b"v2"

        # Restore
        adapter.restore_checkpoint(ref, "cp-1")
        assert adapter.read(ref, "cp.txt") == b"v1"

        adapter.cleanup(ref)

    print("adapter-checkpoint-ok")


# ---------------------------------------------------------------------------
# Test: CustomStrategyConfig validation
# ---------------------------------------------------------------------------


def _config_validation() -> None:
    """Integration test: CustomStrategyConfig validation."""
    # Valid
    cfg = CustomStrategyConfig(
        name="test", module="cleveragents.test", class_name="TestCls"
    )
    assert cfg.name == "test"

    # Empty name
    try:
        CustomStrategyConfig(name="", module="mod", class_name="Cls")
        print("FAIL: Expected ValueError for empty name")
        return
    except ValueError:
        pass

    # Empty module
    try:
        CustomStrategyConfig(name="x", module="", class_name="Cls")
        print("FAIL: Expected ValueError for empty module")
        return
    except ValueError:
        pass

    # Empty class
    try:
        CustomStrategyConfig(name="x", module="mod", class_name="")
        print("FAIL: Expected ValueError for empty class")
        return
    except ValueError:
        pass

    print("config-validation-ok")


# ---------------------------------------------------------------------------
# Test: SandboxFactory custom strategy integration
# ---------------------------------------------------------------------------


def _factory_custom_integration() -> None:
    """Integration test: SandboxFactory with custom strategy registry."""
    registry = SandboxStrategyRegistry(allowed_prefixes=("cleveragents.",))
    registry.register(
        "my_custom",
        "cleveragents.infrastructure.sandbox.strategy_adapter",
        "BuiltInSandboxStrategyAdapter",
    )

    factory = SandboxFactory(custom_registry=registry)
    assert factory.has_custom_strategy("my_custom")
    assert not factory.has_custom_strategy("nonexistent")

    cls = factory.get_custom_strategy_class("my_custom")
    assert cls is BuiltInSandboxStrategyAdapter

    # Factory without registry
    plain_factory = SandboxFactory()
    assert not plain_factory.has_custom_strategy("anything")
    assert plain_factory.get_custom_strategy_class("anything") is None

    print("factory-custom-integration-ok")


# ---------------------------------------------------------------------------
# Main dispatcher
# ---------------------------------------------------------------------------

_TESTS = {
    "sandbox-ref-lifecycle": _sandbox_ref_lifecycle,
    "diff-view-lifecycle": _diff_view_lifecycle,
    "protocol-check": _protocol_check,
    "registry-lifecycle": _registry_lifecycle,
    "adapter-lifecycle": _adapter_lifecycle,
    "adapter-checkpoint": _adapter_checkpoint,
    "config-validation": _config_validation,
    "factory-custom-integration": _factory_custom_integration,
}


def main() -> None:
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <test-name>")
        print(f"Available: {', '.join(sorted(_TESTS))}")
        sys.exit(1)

    test_name = sys.argv[1]
    if test_name not in _TESTS:
        print(f"Unknown test: {test_name}")
        print(f"Available: {', '.join(sorted(_TESTS))}")
        sys.exit(1)

    _TESTS[test_name]()


if __name__ == "__main__":
    main()
