"""boto3/botocore replay for AutoDebug.

This module provides replay of captured AWS S3 calls during test replay.
"""

from __future__ import annotations

import base64
import io
from datetime import datetime
from typing import Any, Callable, Dict, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from autodebug_replay.transcripts import TranscriptIndex

# Original functions
_original_make_request: Optional[Callable] = None

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


class _MockStreamingBody:
    """Mock StreamingBody for S3 GetObject responses."""

    def __init__(self, content: bytes):
        self._content = content
        self._stream = io.BytesIO(content)

    def read(self, amt: Optional[int] = None) -> bytes:
        if amt is None:
            return self._stream.read()
        return self._stream.read(amt)

    def seek(self, offset: int, whence: int = 0) -> int:
        return self._stream.seek(offset, whence)

    def close(self) -> None:
        self._stream.close()

    def __iter__(self):
        return iter(self._content.splitlines(keepends=True))


class _MockHttpResponse:
    """Mock HTTP response for botocore."""

    def __init__(self, status_code: int, headers: Dict[str, str]):
        self.status_code = status_code
        self.headers = headers


def _decode_body(response_data: Dict[str, Any]) -> bytes:
    """Decode recorded body back to bytes."""
    if "body" not in response_data:
        return b""

    body = response_data["body"]
    encoding = response_data.get("body_encoding", "utf-8")

    if encoding == "base64":
        return base64.b64decode(body)
    if isinstance(body, str):
        return body.encode("utf-8")
    return body


def _build_response_dict(record: Dict[str, Any]) -> Dict[str, Any]:
    """Build response dict from recorded data."""
    response_data = record.get("response", {})
    result = {}

    # Add Body for GetObject
    if record["operation"] == "GetObject":
        content = _decode_body(response_data)
        result["Body"] = _MockStreamingBody(content)

    # Add fields from response
    field_mapping = {
        "contents": "Contents",
        "keycount": "KeyCount",
        "istruncated": "IsTruncated",
        "contentlength": "ContentLength",
        "contenttype": "ContentType",
        "etag": "ETag",
        "lastmodified": "LastModified",
    }

    for lower_key, proper_key in field_mapping.items():
        if lower_key in response_data:
            value = response_data[lower_key]
            # Parse datetime strings
            if proper_key == "LastModified" and isinstance(value, str):
                try:
                    value = datetime.fromisoformat(value.replace("Z", "+00:00"))
                except ValueError:
                    pass
            result[proper_key] = value

    return result


def _patched_make_request(self, operation_model, request_dict):
    """Patched Endpoint.make_request that replays from transcript."""
    from autodebug_replay.transcripts import ReplayDivergence

    service_name = operation_model.service_model.service_name
    operation_name = operation_model.name

    # Only replay S3 operations we support
    if service_name != "s3" or operation_name not in {"GetObject", "HeadObject", "ListObjectsV2", "ListObjects"}:
        # For unsupported operations, raise divergence
        raise ReplayDivergence("AWS_UNSUPPORTED_REPLAY", {
            "service": service_name,
            "operation": operation_name,
            "message": f"AWS {service_name}.{operation_name} cannot be replayed",
        })

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

    # Verify service matches
    if record["service"] != service_name:
        raise ReplayDivergence("AWS_SERVICE_MISMATCH", {
            "expected": record["service"],
            "actual": service_name,
        })

    # Verify operation matches
    if record["operation"] != operation_name:
        raise ReplayDivergence("AWS_OPERATION_MISMATCH", {
            "expected": record["operation"],
            "actual": operation_name,
            "service": service_name,
        })

    # Build mock response
    response_data = record.get("response", {})
    status_code = response_data.get("status_code", 200)
    headers = response_data.get("headers", {})

    http_response = _MockHttpResponse(status_code, headers)
    response_dict = _build_response_dict(record)

    return (http_response, response_dict)


def install(index: "TranscriptIndex") -> None:
    """Install boto3/botocore replay patches.

    Args:
        index: TranscriptIndex containing recorded AWS calls
    """
    global _original_make_request, _index

    try:
        import botocore.endpoint
    except ImportError:
        return

    if _original_make_request is not None:
        return  # Already patched

    _index = index
    _original_make_request = botocore.endpoint.Endpoint.make_request
    botocore.endpoint.Endpoint.make_request = _patched_make_request


def uninstall() -> None:
    """Restore original botocore functions."""
    global _original_make_request, _index

    try:
        import botocore.endpoint
    except ImportError:
        return

    if _original_make_request is not None:
        botocore.endpoint.Endpoint.make_request = _original_make_request
        _original_make_request = None

    _index = None
