"""
Practice: answers with no error that are still wrong. Lab 13's scripts 04 (breaking search in a
RAG pipeline) and 03 (checking replies and asking again): check a reply in code, send it back
with a note, count how often the assistant says "I don't know", and turn a search timeout into
an honest message.

Fill in the TODOs, then run the tests:
    pytest test_silent_practice.py        (locally)
or press "Run tests" on the chapter page.
"""
import re

MAX_WORDS = 40
PLACEHOLDER_RE = re.compile(r"\[[^\]]{1,40}\]|\{[^}]{1,40}\}|<[A-Za-z ]{1,30}>")

# The pipelines' fixed messages (the lab's rag.py and 04_chaos_rag.py).
REFUSAL = "I don't know based on the provided documents."
NOT_FOUND = "I couldn't find anything about that in the documents."
UNAVAILABLE = "Search is unavailable right now, so I can't answer from the documents. Please try again in a minute."


def validate_reply(reply, order_id=None):
    """Every rule the reply breaks, as short sentences worded to send back to the model; [] means
    valid. Check, in this order:
      - more than MAX_WORDS words (split on whitespace):
            "reply has 44 words; the limit is 40"
      - order_id given but not in the reply:
            "reply doesn't mention the order number A-3235"
      - a placeholder (PLACEHOLDER_RE) anywhere in it, quoting the first match:
            "reply contains a placeholder: [Your Name]"
    """
    # TODO
    return []


def reask_note(problems):
    """The message sent back with an invalid reply: "That reply is not valid: " + the problems
    joined with "; " + ". Reply again with only the corrected JSON." """
    # TODO
    return ""


def ask_until_valid(ask, validate, reasks=2, feedback=True):
    """Ask, check, and ask again up to `reasks` more times while the reply is invalid.
    `ask(note)` makes one model call and returns the reply text. `note` is None on the first call,
    and on every call when feedback=False (a fresh sample of the same request). With feedback=True,
    each later call gets reask_note() of the previous reply's problems.
    `validate(text)` returns a list of problems.
    Return {"text": last reply, "valid": True/False, "attempts": calls made}."""
    # TODO
    return {"text": "", "valid": False, "attempts": 0}


def is_refusal(answer):
    """The lab's check, in code: lowercase the answer, turn ’ into ', and it's a refusal if it
    contains any of the three fixed messages above (lowercased), or starts with "i don't know"."""
    # TODO
    return False


def refusal_rate(answers):
    """The share of answers that are refusals (0.0 for no answers)."""
    # TODO
    return 0.0


def refusal_alarm(answers, normal_rate, margin=0.2):
    """True when the refusal rate is more than `margin` above its normal level."""
    # TODO
    return False


def answer_defended(question, search, answer):
    """The defended pipeline. `search(question)` returns a list of passages or raises TimeoutError;
    `answer(question, passages)` calls the model.
      - a timeout: try search once more; if it times out again, return UNAVAILABLE
      - no passages: return NOT_FOUND without calling the model
      - otherwise: return answer(question, passages)"""
    # TODO
    return answer(question, search(question))
