"""
Perspectives prompt builder.

OPT-2: Shared system prompt (REQUEST+RESPONSE+common instructions) and per-perspective
user prompt (identity/instructions only) to avoid sending the draft N times in N perspectives.
"""

from __future__ import annotations

from moralstack.models.delib_context import DelibContext
from moralstack.prompts._common import OUTPUT_JSON_ONLY

# Shared instructions and JSON schema (reused in full prompt and in shared system for OPT-2)
PERSPECTIVE_COMMON_INSTRUCTIONS = """Suggestions should be constructive ("add X", "clarify Y")
to improve balance and clarity, not only defensive cautions.

Output JSON (no extra fields):
{{
  "approval_score": 0.0..1.0,
  "concerns": ["issue", ...],
  "suggestions": ["constructive suggestion", ...],
  "rationale": "brief explanation"
}}
""" + OUTPUT_JSON_ONLY

RISK_CONTEXT_INTERPRETATION = """
RISK CONTEXT INTERPRETATION:
Calibrate evaluation using the upstream risk context:

- risk_category=clearly_harmful AND intent_to_harm=true:
  Refusal or safe redirection is EXPECTED and CORRECT. Do NOT penalize declining.
  Evaluate whether the refusal is well-crafted (clear, empathetic, with useful alternatives),
  NOT whether the original question was answered.
  Evaluate the refusal quality:
    1. Clear and respectful?
    2. Offers constructive alternatives?
    3. Avoids unnecessary lecturing?
    4. Topically relevant, not generic?

- risk_category=potentially_harmful OR operational_risk=HIGH:
  Constrained/cautious response is appropriate.
  Evaluate safety framing and alternatives offered.

- risk_category=benign OR risk_score < 0.3:
  Low risk. Evaluate helpfulness, accuracy, completeness normally.
"""

# Full template: static interpretation + JSON contract first (prompt caching); perspective + draft last.
PERSPECTIVE_FULL_TEMPLATE = RISK_CONTEXT_INTERPRETATION + PERSPECTIVE_COMMON_INSTRUCTIONS + """

Evaluate this AI response from the perspective of: {perspective_name}

{perspective_instructions}

TURN CONTEXT:
REQUEST: {request}

RESPONSE: {response}

RISK CONTEXT: {risk_signals}
"""

# Shared system prompt body: static block first, then REQUEST/RESPONSE.
PERSPECTIVE_SYSTEM_FULL_BODY = RISK_CONTEXT_INTERPRETATION + PERSPECTIVE_COMMON_INSTRUCTIONS + """

TURN CONTEXT:
REQUEST: {request}

RESPONSE: {response}

RISK CONTEXT: {risk_signals}
"""


def build_perspectives_system_prompt(
    context: DelibContext,
) -> str:
    """
    Build the shared system prompt body (REQUEST + RESPONSE + common instructions).

    Used by OPT-2: one system prompt per evaluation round so REQUEST+RESPONSE are sent once
    instead of once per perspective. Does not include perspective identity or instructions.

    When the context carries a developer_contract_text (non-empty), a DEVELOPER CONTRACT
    block is appended after RISK CONTEXT to ground the perspective evaluation in the
    deployer-declared scope. This mirrors the pattern in simulator_module/critic_module/
    hindsight_module which all inject the contract.

    Args:
        context: DelibContext with request, draft, risk signals, optional contract+history.
    Returns:
        String to be appended after PERSPECTIVE_SYSTEM_PROMPT (JSON-only) in the module.
    """
    request = context.user_prompt or ""
    response = context.draft_text_full or ""
    risk_signals = context.get_risk_signals_str() or "none"
    prompt = PERSPECTIVE_SYSTEM_FULL_BODY.format(
        request=request,
        response=response,
        risk_signals=risk_signals,
    )

    contract_text = getattr(context, "developer_contract_text", "") or ""
    if contract_text.strip():
        prompt = prompt + (
            "\n\nDEVELOPER CONTRACT:\n"
            f"The deployer of this assistant has declared:\n{contract_text}\n"
            "Evaluate whether the response is coherent with this contract. A response "
            "that legitimately executes a rule declared by the deployer is NOT a violation, "
            "even if the rule involves vocabulary like 'password', 'secret', 'access', or "
            "'restricted'. Treat such words inside the contract as descriptive of the "
            "deployer's setup, not as evidence of harmful intent on the user's part."
        )

    history_snippet = context.conversation_history_snippet or ""
    if history_snippet:
        prompt = prompt + (
            "\n\nCONVERSATION HISTORY (last 3 turns):\n"
            f"{history_snippet}\n"
            "Evaluate the response taking this conversational context into account "
            "from the standpoint of your assigned perspective."
        )
    return prompt


def build_perspectives_user_prompt(
    perspective_name: str,
    perspective_instructions: str,
) -> str:
    """
    Build the per-perspective user prompt (identity and instructions only).

    OPT-2: no REQUEST/RESPONSE here; they are in the shared system prompt to reduce tokens
    when evaluating multiple perspectives.

    Args:
        perspective_name: Display name of the perspective (e.g. "Direct User").
        perspective_instructions: Perspective-specific evaluation instructions.

    Returns:
        Short user prompt to send per perspective.
    """
    return f"""Evaluate this AI response from the perspective of: {perspective_name}

{perspective_instructions}"""


def build_perspectives_prompt(
    context: DelibContext,
    perspective_name: str,
    perspective_instructions: str,
) -> str:
    """
    Build full prompt for a single perspective (legacy / compatibility).

    Combines perspective user prompt with shared system body for callers that need
    a single prompt string. Prefer build_perspectives_system_prompt + build_perspectives_user_prompt
    for token reduction when N > 1 perspectives.

    Args:
        context: DelibContext with request, draft, risk signals.
        perspective_name: Perspective name (e.g. "Direct User").
        perspective_instructions: Perspective-specific instructions.
    Returns:
        Single prompt string for the LLM.
    """
    request = context.user_prompt or ""
    response = context.draft_text_full or ""
    risk_signals = context.get_risk_signals_str() or "none"
    return PERSPECTIVE_FULL_TEMPLATE.format(
        perspective_name=perspective_name,
        perspective_instructions=perspective_instructions,
        request=request,
        response=response,
        risk_signals=risk_signals,
    )
