# Copyright 2024 Klira AI
#
# 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.

"""@mcp_guardrails decorator — MCP context protection.

Uses AUGMENT mode by default (redacts content) — different from
@guardrails which uses block/allow.

In MCP contexts, blocked content is redacted rather than rejected
entirely, preserving the MCP message flow.
"""

from __future__ import annotations

import asyncio
import functools
import logging
from typing import Any, Callable, TypeVar, overload

from klira.sdk.decorators.guardrails import _extract_domain
from klira.sdk.guardrails.engine import GuardrailsEngine

logger = logging.getLogger(__name__)

F = TypeVar("F", bound=Callable[..., Any])


@overload
def mcp_guardrails(func: F) -> F: ...
@overload
def mcp_guardrails(
    *,
    domain: str | None = None,
    policy_set: str | None = None,
) -> Callable[[F], F]: ...


def mcp_guardrails(
    func: F | None = None,
    *,
    domain: str | None = None,
    policy_set: str | None = None,
) -> F | Callable[[F], F]:
    """Decorator that applies MCP-specific guardrails.

    Uses AUGMENT mode by default — redacts blocked content rather than
    raising exceptions, preserving the MCP message flow.

    Args:
        domain: Optional domain for domain-based policy matching. Can be
            overridden at call time via a 'domain', 'url', or 'origin' kwarg.
        policy_set: Optional policy set name.
    """

    def decorator(fn: F) -> F:
        is_async = asyncio.iscoroutinefunction(fn)

        if is_async:

            @functools.wraps(fn)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                engine = GuardrailsEngine.get_instance()
                resolved_domain = _extract_domain(**kwargs) or domain

                # Execute the function first (AUGMENT mode)
                result = await fn(*args, **kwargs)

                # Check output and redact if needed
                output_text = str(result) if result is not None else ""
                if output_text:
                    output_result = engine.check_output(
                        output_text, domain=resolved_domain, policy_set=policy_set
                    )
                    if not output_result.allowed:
                        logger.info(
                            "MCP guardrails: content redacted by policy %s",
                            output_result.matched_policies[0].policy.id
                            if output_result.matched_policies
                            else "unknown",
                        )
                        return (
                            output_result.violation_response
                            or "[Content redacted by policy]"
                        )

                return result

            return async_wrapper  # type: ignore[return-value]
        else:

            @functools.wraps(fn)
            def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
                engine = GuardrailsEngine.get_instance()
                resolved_domain = _extract_domain(**kwargs) or domain

                # Execute the function first (AUGMENT mode)
                result = fn(*args, **kwargs)

                # Check output and redact if needed
                output_text = str(result) if result is not None else ""
                if output_text:
                    output_result = engine.check_output(
                        output_text, domain=resolved_domain, policy_set=policy_set
                    )
                    if not output_result.allowed:
                        logger.info(
                            "MCP guardrails: content redacted by policy %s",
                            output_result.matched_policies[0].policy.id
                            if output_result.matched_policies
                            else "unknown",
                        )
                        return (
                            output_result.violation_response
                            or "[Content redacted by policy]"
                        )

                return result

            return sync_wrapper  # type: ignore[return-value]

    if func is not None:
        return decorator(func)
    return decorator
