# ========= 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 asyncio
import os
import shutil
import tempfile
import time
import uuid
from pathlib import Path

from mirage import MountMode, Workspace
from mirage.resource.disk import DiskResource
from mirage.resource.ram import RAMResource
from mirage.types import ConsistencyPolicy

try:
    from mirage.resource.redis import RedisResource
    _REDIS_IMPORT_OK = True
except ImportError:
    RedisResource = None
    _REDIS_IMPORT_OK = False


def _banner(title: str) -> None:
    print(f"\n=== {title} ===")


async def disk_demo() -> None:
    """Disk has mtime fingerprints. ALWAYS detects external mutation."""
    _banner("disk + LAZY — external mutation NOT detected (cached stale)")
    lazy_root = Path(tempfile.mkdtemp(prefix="mirage-disk-lazy-"))
    try:
        (lazy_root / "file.txt").write_bytes(b"v1")
        resource = DiskResource(root=str(lazy_root))
        ws = Workspace(
            {"/data": (resource, MountMode.WRITE)},
            mode=MountMode.WRITE,
            consistency=ConsistencyPolicy.LAZY,
        )
        io1 = await ws.execute("cat /data/file.txt")
        print(f"  first  read (v1 expected)        : "
              f"{(await io1.materialize_stdout())!r}")
        time.sleep(1.1)
        (lazy_root / "file.txt").write_bytes(b"v2-external")
        io2 = await ws.execute("cat /data/file.txt")
        print(f"  second read after external write : "
              f"{(await io2.materialize_stdout())!r}  <-- LAZY, stale")
    finally:
        shutil.rmtree(lazy_root, ignore_errors=True)

    _banner("disk + ALWAYS — external mutation detected (fresh)")
    always_root = Path(tempfile.mkdtemp(prefix="mirage-disk-always-"))
    try:
        (always_root / "file.txt").write_bytes(b"v1")
        resource = DiskResource(root=str(always_root))
        ws = Workspace(
            {"/data": (resource, MountMode.WRITE)},
            mode=MountMode.WRITE,
            consistency=ConsistencyPolicy.ALWAYS,
        )
        io1 = await ws.execute("cat /data/file.txt")
        print(f"  first  read (v1 expected)        : "
              f"{(await io1.materialize_stdout())!r}")
        time.sleep(1.1)
        (always_root / "file.txt").write_bytes(b"v2-external")
        io2 = await ws.execute("cat /data/file.txt")
        print(f"  second read after external write : "
              f"{(await io2.materialize_stdout())!r}  <-- ALWAYS, fresh")
    finally:
        shutil.rmtree(always_root, ignore_errors=True)


async def ram_demo() -> None:
    """RAM has no fingerprint. ALWAYS falls back to LAZY.

    Workspace-originated writes still invalidate the cache.
    """
    _banner("RAM + ALWAYS — no fingerprint, LAZY fallback serves stale")
    resource = RAMResource()
    resource._store.files["/file.txt"] = b"v1"
    ws = Workspace(
        {"/data": (resource, MountMode.WRITE)},
        mode=MountMode.WRITE,
        consistency=ConsistencyPolicy.ALWAYS,
    )
    io1 = await ws.execute("cat /data/file.txt")
    print(f"  first  read (v1 expected)              : "
          f"{(await io1.materialize_stdout())!r}")
    resource._store.files["/file.txt"] = b"v2-external"
    io2 = await ws.execute("cat /data/file.txt")
    print(f"  second read after external mutation    : "
          f"{(await io2.materialize_stdout())!r}  <-- ALWAYS→LAZY, stale")

    _banner("RAM — workspace-originated write invalidates cache (fresh)")
    resource2 = RAMResource()
    resource2._store.files["/file.txt"] = b"v1"
    ws2 = Workspace(
        {"/data": (resource2, MountMode.WRITE)},
        mode=MountMode.WRITE,
        consistency=ConsistencyPolicy.ALWAYS,
    )
    io3 = await ws2.execute("cat /data/file.txt")
    print(f"  first read (v1 expected)               : "
          f"{(await io3.materialize_stdout())!r}")
    await ws2.execute('echo -n "v2-via-workspace" > /data/file.txt')
    io4 = await ws2.execute("cat /data/file.txt")
    print(f"  read after workspace-owned write       : "
          f"{(await io4.materialize_stdout())!r}  <-- cache invalidated")


async def redis_demo() -> None:
    if not _REDIS_IMPORT_OK:
        _banner("redis — SKIPPED (mirage-ai[redis] extra not installed)")
        return
    redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379")
    prefix = f"mirage_consistency_demo_{uuid.uuid4().hex[:8]}"

    _banner("redis + ALWAYS — no fingerprint, LAZY fallback serves stale")
    try:
        resource = RedisResource(url=redis_url, key_prefix=prefix)
    except Exception as exc:
        print(f"  SKIPPED (could not connect to {redis_url}): {exc}")
        return

    # Prime: write v1 through the workspace so it lands in the resource
    ws_primer = Workspace(
        {"/data": (resource, MountMode.WRITE)},
        mode=MountMode.WRITE,
        consistency=ConsistencyPolicy.LAZY,
    )
    await ws_primer.execute('echo -n "v1" > /data/file.txt')
    try:
        ws = Workspace(
            {"/data": (resource, MountMode.WRITE)},
            mode=MountMode.WRITE,
            consistency=ConsistencyPolicy.ALWAYS,
        )
        io1 = await ws.execute("cat /data/file.txt")
        print(f"  first  read (v1 expected)              : "
              f"{(await io1.materialize_stdout())!r}")

        # Simulate external mutation: write directly through another workspace
        # instance. The target cache (ws._cache) never sees the other write,
        # so it still serves v1 under ALWAYS (no fingerprint to compare).
        ws_other = Workspace(
            {
                "/data": (RedisResource(url=redis_url,
                                        key_prefix=prefix), MountMode.WRITE)
            },
            mode=MountMode.WRITE,
        )
        await ws_other.execute('echo -n "v2-external" > /data/file.txt')

        io2 = await ws.execute("cat /data/file.txt")
        print(f"  second read after external mutation    : "
              f"{(await io2.materialize_stdout())!r}  <-- ALWAYS→LAZY, stale")

        # Workspace-owned write invalidates the local cache
        _banner("redis — workspace-originated write invalidates cache (fresh)")
        await ws.execute('echo -n "v3-via-workspace" > /data/file.txt')
        io3 = await ws.execute("cat /data/file.txt")
        print(f"  read after workspace-owned write       : "
              f"{(await io3.materialize_stdout())!r}  <-- cache invalidated")
    finally:
        # Clean up keys we created
        try:
            import redis
            client = redis.Redis.from_url(redis_url)
            for key in client.scan_iter(match=f"{prefix}:*"):
                client.delete(key)
        except Exception:
            pass


async def main() -> None:
    await disk_demo()
    await ram_demo()
    await redis_demo()


if __name__ == "__main__":
    asyncio.run(main())
