"""Redis replay for AutoDebug.

This module provides replay of captured Redis commands during test replay.
"""

from __future__ import annotations

from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from autodebug_replay.transcripts import TranscriptIndex

# Original functions
_original_execute_command: Optional[Callable] = None
_original_pipeline_execute: Optional[Callable] = None

# Global transcript index
_index: Optional["TranscriptIndex"] = None


def _deserialize_result(result: Any) -> Any:
    """Deserialize a recorded result back to original form."""
    if result is None:
        return None
    if isinstance(result, dict) and "__bytes__" in result:
        return bytes.fromhex(result["__bytes__"])
    if isinstance(result, (str, int, float, bool)):
        return result
    if isinstance(result, list):
        return [_deserialize_result(r) for r in result]
    if isinstance(result, dict):
        return {k: _deserialize_result(v) for k, v in result.items()}
    return result


def _patched_execute_command(self, *args, **kwargs):
    """Patched Redis.execute_command that replays from transcript."""
    from autodebug_replay.transcripts import ReplayDivergence

    command = args[0].upper() if args else "UNKNOWN"

    # Get next Redis record from transcript
    record = _index.expect("REDIS")

    # Handle pipeline records separately
    if record.get("is_pipeline"):
        raise ReplayDivergence("REDIS_UNEXPECTED_PIPELINE", {
            "expected": "PIPELINE",
            "actual": command,
            "message": "Expected pipeline execution but got single command",
        })

    # Verify command matches
    if record["command"] != command:
        raise ReplayDivergence("REDIS_COMMAND_MISMATCH", {
            "expected": record["command"],
            "actual": command,
            "key": record.get("key"),
        })

    # Return recorded result
    return _deserialize_result(record["result"])


def _patched_pipeline_execute(self, raise_on_error=True):
    """Patched Pipeline.execute that replays from transcript."""
    from autodebug_replay.transcripts import ReplayDivergence

    # Get next Redis record from transcript
    record = _index.expect("REDIS")

    # Verify this is a pipeline record
    if not record.get("is_pipeline"):
        raise ReplayDivergence("REDIS_EXPECTED_PIPELINE", {
            "expected_command": record["command"],
            "actual": "PIPELINE",
            "message": "Expected single command but got pipeline",
        })

    # Return recorded results
    results = record.get("results", [])
    return [_deserialize_result(r) for r in results]


def install(index: "TranscriptIndex") -> None:
    """Install Redis replay patches.

    Args:
        index: TranscriptIndex containing recorded Redis commands
    """
    global _original_execute_command, _original_pipeline_execute, _index

    try:
        import redis
    except ImportError:
        return

    if _original_execute_command is not None:
        return  # Already patched

    _index = index

    # Patch Redis.execute_command
    _original_execute_command = redis.Redis.execute_command
    redis.Redis.execute_command = _patched_execute_command

    # Patch Pipeline.execute
    if hasattr(redis, "client") and hasattr(redis.client, "Pipeline"):
        _original_pipeline_execute = redis.client.Pipeline.execute
        redis.client.Pipeline.execute = _patched_pipeline_execute


def uninstall() -> None:
    """Restore original Redis functions."""
    global _original_execute_command, _original_pipeline_execute, _index

    try:
        import redis
    except ImportError:
        return

    if _original_execute_command is not None:
        redis.Redis.execute_command = _original_execute_command
        _original_execute_command = None

    if _original_pipeline_execute is not None:
        if hasattr(redis, "client") and hasattr(redis.client, "Pipeline"):
            redis.client.Pipeline.execute = _original_pipeline_execute
        _original_pipeline_execute = None

    _index = None
