"""Derivation backend v0 -- HTTP client to the Studio 122B (or any
OpenAI-compatible /v1/chat/completions endpoint).

Two call modes:

  derive_cited(prompt, retrieved)   -- the model receives the retrieved
                                       section text inline (RAG) and is
                                       asked to quote verbatim + derive
                                       + emit a Cite line whose source
                                       matches retrieved["source"]/section.

  derive_uncited(prompt)            -- the model receives no source text
                                       and is asked to answer in plain
                                       prose without a Cite line. Used
                                       when the classifier says
                                       needs_cite=False.

Both go through the same LLM endpoint; the difference is in the
system + user prompts.  Default endpoint is the SSH-tunneled Studio
122B at localhost:8082; override with --endpoint / --model.
"""

from __future__ import annotations
import json
import urllib.request

LLM_URL_DEFAULT = "http://localhost:8082/v1/chat/completions"
MODEL_DEFAULT = "mlx-community/Qwen3.5-122B-A10B-heretic-v2-2.34bit-msq"


SYSTEM_CITED = (
    "You are a Deductive panelist.  You will receive a user question "
    "AND the verbatim text of one section of an authoritative source.  "
    "Answer the question by (a) opening with Yes / No / or a short claim, "
    "(b) quoting the load-bearing clause from the supplied section text "
    "VERBATIM (in double quotes), (c) deriving the conclusion in 1-3 short "
    "sentences, and (d) ending with a single 'Cite:' line whose source and "
    "section match the supplied section's header.\n\n"
    "Quotation rules (CRITICAL):\n"
    "  - The quoted clause MUST be a single contiguous substring of the "
    "supplied text.  Do NOT insert '...' ellipses inside the quote.  Do "
    "NOT splice across paragraphs or bullets.\n"
    "  - If you must shorten, pick a single sentence or phrase that "
    "already appears verbatim.  A shorter precise quote beats a longer "
    "stitched one.\n"
    "  - You may drop markdown markers like **bold** when quoting -- the "
    "verifier ignores those.\n"
    "  - Do not cite anything outside the supplied section."
)

SYSTEM_EMPIRICAL = (
    "You are an Empirical panelist.  You will receive a user question "
    "AND the verbatim text of one section of an authoritative source.  "
    "Where the Deductive panelist quotes the source's general invariant, "
    "you target the OBSERVABLE BEHAVIOR clause -- the part of the section "
    "that describes what HAPPENS when the operation runs.  Look for "
    "'shall fail if', 'shall return', 'shall set errno to', 'on success "
    "returns', 'the function returns', 'when X then Y', etc.\n\n"
    "Answer by (a) opening with 'Observed:' or 'In practice:' (NOT 'Yes' "
    "or 'No' -- those are Deductive openers), (b) quoting the OBSERVED-"
    "BEHAVIOR clause VERBATIM in double quotes, (c) framing your "
    "derivation as 'this means an implementation running this would see "
    "X' / 'callers observe Y' style -- 1-3 sentences, and (d) ending with "
    "'Cite: <source> section <section>'.\n\n"
    "Quotation rules (same as Deductive): single contiguous substring, no "
    "ellipses, no splicing across paragraphs.  Markdown markers may be "
    "dropped.  Do not cite anything outside the supplied section.\n\n"
    "If the supplied section text has no observable-behavior clause "
    "(it's pure prose / framing), return: "
    '{"prompt":"","target":""} and let the orchestrator route uncited.'
)


SYSTEM_UNCITED = (
    "You are a Deductive panelist with NO source text on hand.  Answer "
    "in plain prose, concise (2-4 sentences).  Do NOT include a 'Cite:' "
    "line.\n\n"
    "Critically: if the question asks about a specific TECHNICAL FACT "
    "(an RFC clause, a POSIX behavior, a Python language guarantee, a "
    "Rail compiler invariant, a specific library API), you do NOT have "
    "the source on hand.  In that case, refuse honestly: open with 'I "
    "don't have a verified source on hand for this' and explain why a "
    "grounded answer would require pulling the source.  Do NOT invent "
    "factual claims about technical specifics.\n\n"
    "It is appropriate to answer when the question is reasoning/opinion "
    "framing (compare X vs Y, when is X the wrong call, how should I "
    "think about Z) -- those don't need a source.  But if it's a "
    "factual technical claim and you have no retrieved text, refuse."
)


def _post(url: str, body: dict, timeout: int = 180) -> dict:
    data = json.dumps(body).encode()
    req = urllib.request.Request(
        url, data=data, headers={"Content-Type": "application/json"}
    )
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return json.loads(r.read())


def derive_empirical(
    prompt: str,
    retrieved: dict,
    *,
    endpoint: str = LLM_URL_DEFAULT,
    model: str = MODEL_DEFAULT,
    max_tokens: int = 400,
    temperature: float = 0.2,
) -> str:
    """Empirical-flavored RAG derivation over the same retrieved section.

    Emits 'Observed: ...' + verbatim quote of the observable-behavior clause
    + derivation framed as runtime observation + Cite line.
    """
    user_msg = (
        f"Question: {prompt}\n\n"
        f"Source: {retrieved['source']} section {retrieved['section']}\n"
        f"Section text:\n<<<\n{retrieved['text']}\n>>>\n\n"
        f"Construct your Empirical answer per the system instructions.  "
        f"The Cite line must read exactly 'Cite: {retrieved['source']} "
        f"section {retrieved['section']}'."
    )
    resp = _post(endpoint, {
        "model": model,
        "messages": [
            {"role": "system", "content": SYSTEM_EMPIRICAL},
            {"role": "user", "content": user_msg},
        ],
        "max_tokens": max_tokens,
        "temperature": temperature,
    })
    return resp["choices"][0]["message"]["content"]


def derive_cited(
    prompt: str,
    retrieved: dict,
    *,
    endpoint: str = LLM_URL_DEFAULT,
    model: str = MODEL_DEFAULT,
    max_tokens: int = 400,
    temperature: float = 0.2,
) -> str:
    user_msg = (
        f"Question: {prompt}\n\n"
        f"Source: {retrieved['source']} section {retrieved['section']}\n"
        f"Section text:\n<<<\n{retrieved['text']}\n>>>\n\n"
        f"Construct your answer per the system instructions.  The Cite "
        f"line must read exactly 'Cite: {retrieved['source']} section "
        f"{retrieved['section']}'."
    )
    resp = _post(endpoint, {
        "model": model,
        "messages": [
            {"role": "system", "content": SYSTEM_CITED},
            {"role": "user", "content": user_msg},
        ],
        "max_tokens": max_tokens,
        "temperature": temperature,
    })
    return resp["choices"][0]["message"]["content"]


def derive_uncited(
    prompt: str,
    *,
    endpoint: str = LLM_URL_DEFAULT,
    model: str = MODEL_DEFAULT,
    max_tokens: int = 400,
    temperature: float = 0.3,
) -> str:
    resp = _post(endpoint, {
        "model": model,
        "messages": [
            {"role": "system", "content": SYSTEM_UNCITED},
            {"role": "user", "content": prompt},
        ],
        "max_tokens": max_tokens,
        "temperature": temperature,
    })
    return resp["choices"][0]["message"]["content"]
