"""Solution for the Lab 09 practice: scoring a voice agent."""
import math
import re

ONES = {"zero": "0", "oh": "0", "o": "0", "one": "1", "two": "2", "three": "3", "four": "4",
        "five": "5", "six": "6", "seven": "7", "eight": "8", "nine": "9"}
TEENS = {"ten": "10", "eleven": "11", "twelve": "12", "thirteen": "13", "fourteen": "14",
         "fifteen": "15", "sixteen": "16", "seventeen": "17", "eighteen": "18", "nineteen": "19"}
TENS = {"twenty": "2", "thirty": "3", "forty": "4", "fifty": "5", "sixty": "6", "seventy": "7",
        "eighty": "8", "ninety": "9"}


def spoken_digits(text: str) -> str:
    words = re.findall(r"[a-z]+|\d+", text.lower().replace("-", " "))
    out, i = [], 0
    while i < len(words):
        w = words[i]
        if w.isdigit():
            out.append(w)
        elif w in ONES:
            out.append(ONES[w])
        elif w in TEENS:
            out.append(TEENS[w])
        elif w in TENS:
            nxt = words[i + 1] if i + 1 < len(words) else ""
            if nxt in ONES and nxt not in ("zero", "oh", "o"):
                out.append(TENS[w] + ONES[nxt])
                i += 1
            else:
                out.append(TENS[w] + "0")
        i += 1
    return "".join(out)


def _key(name: str) -> str:
    return re.sub(r"[^a-z]", "", (name or "").lower())


def task_result(final: list[dict], tool_log: list[str], expect: dict) -> dict:
    problems = []
    e = expect.get("appointment")
    if e:
        found = [a for a in final if a["phone"] == e["phone"]]
        if not found:
            problems.append("no appointment")
        else:
            a = found[-1]
            if _key(a["name"]) != _key(e["name"]):
                problems.append("wrong name")
            if a["date"] != e["date"]:
                problems.append("wrong date")
            if a["time"] != e["time"]:
                problems.append("wrong time")
    if "absent" in expect and any(a["id"] == expect["absent"] for a in final):
        problems.append("not cancelled")
    for tool, needs in expect.get("tools_before", {}).items():
        if tool in tool_log and not all(n in tool_log[:tool_log.index(tool)] for n in needs):
            problems.append(f"{tool} before {', '.join(needs)}")
    for tool in expect.get("forbidden_tools", []):
        if tool in tool_log:
            problems.append(f"called {tool}")
    return {"success": not problems, "problems": problems}


def turn_latency(turn: dict) -> dict:
    heard = turn["play_start"] + turn["leading_silence"]
    return {"endpointing": turn["eot"] - turn["caller_speech_end"],
            "llm_first_token": turn["llm_first_token"] - turn["eot"],
            "first_sentence_ready": turn["first_sentence"] - turn["llm_first_token"],
            "tts_first_byte": turn["tts_first_byte"] - turn["first_sentence"],
            "playback_and_lead": heard - turn["tts_first_byte"],
            "voice_to_voice": heard - turn["caller_speech_end"]}


def wilson(k: int, n: int, z: float = 1.96) -> tuple[float, float]:
    if n == 0:
        return 0.0, 1.0
    p = k / n
    centre = (p + z * z / (2 * n)) / (1 + z * z / n)
    half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / (1 + z * z / n)
    return max(0.0, centre - half), min(1.0, centre + half)
