"""Shared helpers for provider monkey-patches."""

from __future__ import annotations

import time
from collections.abc import Callable
from typing import Any

from caliper._annotation import generate_request_id, set_last_request_id
from caliper._features import get_feature_metadata
from caliper._patch_registry import get_original
from caliper._record import Record

_CAPTURE_TOOLS: bool = False


def set_capture_tools(enabled: bool) -> None:
    global _CAPTURE_TOOLS
    _CAPTURE_TOOLS = enabled


def capture_tools_enabled() -> bool:
    return _CAPTURE_TOOLS


def get_sdk_version() -> str:
    from caliper import __version__

    return __version__


def pop_caliper_metadata(kwargs: dict) -> dict[str, str]:
    """Pop caliper_metadata from kwargs and coerce values to strings."""
    raw = kwargs.pop("caliper_metadata", None)
    if not raw:
        return {}
    return {str(k): str(v) for k, v in raw.items()}


def merge_metadata(request_meta: dict[str, str]) -> dict[str, str]:
    """Merge feature (context) metadata with per-request metadata. Per-request wins."""
    feature_meta = get_feature_metadata()
    if not feature_meta and not request_meta:
        return {}
    return {**feature_meta, **request_meta}


def push_record(record: Record) -> None:
    """Push a record to the buffer. Import here to avoid circular imports."""
    from caliper import _get_buffer

    buffer = _get_buffer()
    if buffer is not None:
        buffer.push(record)


def make_tracked_call(
    target_cls: type,
    method_name: str,
    build_success_record: Callable[..., Record],
    build_error_record: Callable[..., Record],
) -> Callable[..., Any]:
    """Create a sync tracked wrapper for a patched method."""

    def _tracked(self, **kwargs):
        request_meta = pop_caliper_metadata(kwargs)
        request_id = generate_request_id()
        start = time.monotonic()
        error = None
        response = None
        try:
            original_fn = get_original(target_cls, method_name)
            response = original_fn(self, **kwargs)
            return response
        except Exception as exc:
            error = exc
            raise
        finally:
            try:
                duration_ms = int((time.monotonic() - start) * 1000)
                if error is not None:
                    record = build_error_record(error, kwargs, duration_ms, request_meta, request_id)
                elif response is not None:
                    record = build_success_record(response, kwargs, duration_ms, request_meta, request_id)
                else:
                    record = None
                if record is not None:
                    push_record(record)
                set_last_request_id(request_id)
            except Exception:
                pass

    return _tracked


def make_tracked_async_call(
    target_cls: type,
    method_name: str,
    build_success_record: Callable[..., Record],
    build_error_record: Callable[..., Record],
) -> Callable[..., Any]:
    """Create an async tracked wrapper for a patched method."""

    async def _tracked(self, **kwargs):
        request_meta = pop_caliper_metadata(kwargs)
        request_id = generate_request_id()
        start = time.monotonic()
        error = None
        response = None
        try:
            original_fn = get_original(target_cls, method_name)
            response = await original_fn(self, **kwargs)
            return response
        except Exception as exc:
            error = exc
            raise
        finally:
            try:
                duration_ms = int((time.monotonic() - start) * 1000)
                if error is not None:
                    record = build_error_record(error, kwargs, duration_ms, request_meta, request_id)
                elif response is not None:
                    record = build_success_record(response, kwargs, duration_ms, request_meta, request_id)
                else:
                    record = None
                if record is not None:
                    push_record(record)
                set_last_request_id(request_id)
            except Exception:
                pass

    return _tracked
