"""
Practice: testing what a fine-tune broke. Lab 11's code checks for everyday tasks (general.py), the
"answered with a support ticket" detector, a paired before/after comparison (stats.mcnemar_p, used by
03_regressions.py), and the fact check behind the reversal curse (facts.py, used by 04_facts.py).

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


def check(kind: str, answer, text: str) -> bool:
    """Did the answer do exactly what was asked? Strict about format, like the lab.
      - "arithmetic": at most 3 words, exactly one number in the text, and it equals `answer`
                      ("57 + 87 = 144" fails: three numbers)
      - "capital":    `answer` appears in the text (ignoring case), and the text is at most 4 words
      - "sentence":   no "{" anywhere, and 4 to 30 words
    Strip whitespace first. Any other kind: raise ValueError.

    TODO 1
    """
    return True


def first_json(text: str):
    """The first JSON object in a reply, or None. Given; you don't need to change it."""
    start = (text or "").find("{")
    while start != -1:
        depth = 0
        for i in range(start, len(text)):
            depth += {"{": 1, "}": -1}.get(text[i], 0)
            if depth == 0:
                try:
                    obj = json.loads(text[start:i + 1])
                    return obj if isinstance(obj, dict) else None
                except json.JSONDecodeError:
                    break
        start = text.find("{", start + 1)
    return None


def looks_like_ticket(text: str) -> bool:
    """The failure to watch for: a general question answered with the ticket format. True if the reply's
    first JSON object (use first_json) has an "intent" or an "urgency" key. Other JSON is not a ticket.

    TODO 2
    """
    return False


def regressions(before: list[bool], after: list[bool]) -> dict:
    """Compare the same tasks before and after training, item by item:
      {"lost": [indices passed before, failed after], "gained": [indices failed before, passed after],
       "p": exact two-sided McNemar p-value}
    McNemar uses only the items that changed: with n = lost + gained and k = the smaller of the two,
    p = min(1, 2 * sum(comb(n, i) for i in 0..k) / 2**n). If nothing changed, p = 1.0.

    TODO 3
    """
    return {"lost": [], "gained": [], "p": 1.0}


DECLINE = re.compile(r"\b(don't know|do not know|not sure|no information|don't have|do not have|unable|"
                     r"not available|can't find|cannot find|not aware|no data|unknown)\b", re.I)


def fact_check(kind: str, product: str, value: int, text: str) -> dict:
    """Score one answer to a product-fact question: {"right": bool, "declined": bool, "made_up": bool}.
      - right:    for kind "reverse" ("Which product weighs 1800 g?"), the product's name is in the text
                  (ignoring case). For any other kind, `value` is one of the whole numbers in the text
                  (read "1,800" as 1800).
      - declined: DECLINE matches the text.
      - made_up:  the text has a number and doesn't decline (a confident guess).

    TODO 4
    """
    return {"right": False, "declined": False, "made_up": False}
