"""
Redactors plugin protocol and helpers.

This module defines the `BaseRedactor` protocol and a sequential helper to
apply a list of redactors in deterministic order. All APIs are async-first and
non-blocking by design.
"""

from __future__ import annotations

from typing import Iterable, Protocol, runtime_checkable

import orjson

from ...core import diagnostics
from ...metrics.metrics import MetricsCollector, plugin_timer
from ..loader import register_builtin
from .field_blocker import FieldBlockerRedactor
from .field_mask import FieldMaskRedactor
from .regex_mask import RegexMaskRedactor
from .string_truncate import StringTruncateRedactor
from .url_credentials import UrlCredentialsRedactor


@runtime_checkable
class BaseRedactor(Protocol):
    """Authoring contract for redactors that sanitize events.

    Implementations MUST be async and non-blocking. Redactors receive and return
    event mappings; they should preserve structure and contain failures. Any I/O
    must be awaitable. Idempotent behavior is recommended when feasible.
    """

    name: str

    async def start(self) -> None:  # pragma: no cover - optional lifecycle
        """Initialize redactor resources (optional)."""

    async def stop(self) -> None:  # pragma: no cover - optional lifecycle
        """Release redactor resources (optional)."""

    async def redact(self, event: dict) -> dict:  # noqa: D401
        """Return a redacted copy of the input mapping without raising upstream."""

    async def health_check(self) -> bool:  # pragma: no cover - optional
        """Return True if the redactor is healthy. Default: assume healthy."""
        return True


def _deep_copy_event(event: dict) -> dict:
    """Deep-copy a JSON-like event dict via orjson roundtrip.

    Events are JSON-serializable by contract, so orjson.loads(orjson.dumps(...))
    produces a fully independent copy with no shared nested references.
    """
    result: dict = orjson.loads(orjson.dumps(event))
    return result


async def redact_in_order(
    event: dict,
    redactors: Iterable[BaseRedactor],
    *,
    metrics: MetricsCollector | None = None,
) -> dict:
    """Apply redactors sequentially and deterministically.

    - Each redactor runs in the given order inside a plugin timing context
    - Exceptions are contained; the last good snapshot is preserved
    - Metrics are recorded via the shared metrics collector when enabled
    """

    current: dict = dict(event)
    for r in list(redactors):
        plugin_name = getattr(r, "__class__", type(r)).__name__
        try:
            async with plugin_timer(metrics, plugin_name):
                snapshot = _deep_copy_event(current)
                next_event = await r.redact(snapshot)
            # Shallow replacement to preserve mapping semantics
            if isinstance(next_event, dict):
                current = next_event
            # Record redaction operational metrics (Story 4.71)
            if metrics is not None:
                redacted = getattr(r, "last_redacted_count", 0)
                if redacted:
                    await metrics.record_redacted_fields(redacted)
                violations = getattr(r, "last_policy_violations", 0)
                if violations:
                    await metrics.record_policy_violations(violations)
        except Exception as exc:
            # Contain failure and continue with last good snapshot
            # Errors are recorded by plugin_timer when metrics is enabled
            try:
                diagnostics.warn(
                    "redactor",
                    "redactor exception",
                    redactor=getattr(r, "name", plugin_name),
                    reason=str(exc),
                )
            except Exception:
                pass
            continue
    return current


# Register built-ins with alias support
register_builtin(
    "fapilog.redactors",
    "field_mask",
    FieldMaskRedactor,
    aliases=["field-mask"],
)
register_builtin(
    "fapilog.redactors",
    "regex_mask",
    RegexMaskRedactor,
    aliases=["regex-mask"],
)
register_builtin(
    "fapilog.redactors",
    "url_credentials",
    UrlCredentialsRedactor,
    aliases=["url-credentials"],
)
register_builtin(
    "fapilog.redactors",
    "field_blocker",
    FieldBlockerRedactor,
    aliases=["field-blocker"],
)
register_builtin(
    "fapilog.redactors",
    "string_truncate",
    StringTruncateRedactor,
    aliases=["string-truncate"],
)

__all__ = [
    "BaseRedactor",
    "redact_in_order",
    "FieldBlockerRedactor",
    "FieldMaskRedactor",
    "RegexMaskRedactor",
    "StringTruncateRedactor",
    "UrlCredentialsRedactor",
]
