"""Midterm Exam: student-ID-seeded randomized questions"""

import hashlib
import random

from checker._feedback import show_exam_result, show_pass, show_summary, show_error, _display_html
from checker._tracking import init_tracking, log_attempt, is_blocked, _capture_source

_WEEK = "midterm"
_student_id = None
_data = {}
_expected = {}
_results = {}
_attempts = {}
_scores = {}  # for partial scoring: {"Q3": (passed, total), ...}

_Q_LABELS = {
    "Q1": "Q1 算術運算",
    "Q2": "Q2 字串操作",
    "Q3": "Q3 條件判斷",
    "Q4": "Q4 迴圈累加",
    "Q5": "Q5 進階迴圈",
    "Q6": "Q6 函數定義",
    "Q7": "Q7 串列操作",
}
_Q_POINTS = {"Q1": 10, "Q2": 10, "Q3": 15, "Q4": 15, "Q5": 15, "Q6": 15, "Q7": 20}


def _make_rng(student_id, question_id):
    seed = hashlib.sha256(f"{student_id}:{question_id}".encode()).hexdigest()
    return random.Random(seed)


def _generate_all(student_id):
    """Generate all question data and expected answers. Returns (data_dict, expected_dict)."""
    data = {}
    expected = {}

    # --- Q1: Arithmetic (read code) ---
    rng = _make_rng(student_id, "Q1")
    num_fields = rng.randint(3, 7)
    per_field = rng.randint(1000, 3000)
    total_harvest = num_fields * per_field
    data["Q1"] = {"total_harvest": total_harvest, "num_fields": num_fields}
    expected["Q1"] = total_harvest // num_fields  # avg (int division)
    expected["Q1_remainder"] = total_harvest % num_fields

    # --- Q2: String ops (read code) ---
    rng = _make_rng(student_id, "Q2")
    crops = ["Tomato", "Corn", "Rice", "Wheat", "Soybean", "Potato", "Cabbage", "Carrot"]
    crop = rng.choice(crops)
    weight = rng.randint(500, 999) / 10
    data["Q2"] = {"raw_report": f"  {crop}, {weight}kg  "}
    parts = f"  {crop}, {weight}kg  ".strip().split(", ")
    expected["Q2"] = parts  # [crop, f"{weight}kg"]

    # --- Q3: Conditionals (fill-in-the-blank function, partial scoring) ---
    rng = _make_rng(student_id, "Q3")
    excellent = rng.choice([85, 88, 90, 92])
    good = excellent - rng.choice([12, 15, 18])
    fair = good - rng.choice([12, 15, 18])
    data["Q3"] = {"excellent": excellent, "good": good, "fair": fair}
    # Generate 5 test cases spanning all grade ranges
    q3_cases = [
        (excellent + 5, "excellent"),      # above excellent
        (excellent, "excellent"),           # exactly excellent
        (good + 1, "good"),                # between good and excellent
        (fair + 1, "fair"),                # between fair and good
        (fair - 10, "fail"),               # below fair
    ]
    expected["Q3_cases"] = q3_cases

    # --- Q4: for loop accumulation (read code) ---
    rng = _make_rng(student_id, "Q4")
    temps = [rng.randint(20, 38) for _ in range(7)]
    threshold = rng.choice([28, 30, 32])
    data["Q4"] = {"temperatures": temps, "threshold": threshold}
    count = sum(1 for t in temps if t > threshold)
    total = sum(t for t in temps if t > threshold)
    expected["Q4"] = (count, total)

    # --- Q5: enumerate/zip (fill-in-the-blank) ---
    rng = _make_rng(student_id, "Q5")
    all_crops = ["稻米", "玉米", "大豆", "小麥", "地瓜", "花生", "高粱", "芋頭"]
    q5_crops = rng.sample(all_crops, 4)
    q5_yields = [rng.randint(400, 1500) for _ in range(4)]
    data["Q5"] = {"crops": q5_crops, "yields": q5_yields}
    max_idx = q5_yields.index(max(q5_yields))
    expected["Q5"] = q5_crops[max_idx]

    # --- Q6: Function definition (write function, partial scoring) ---
    rng = _make_rng(student_id, "Q6")
    high_threshold = rng.choice([100, 120, 150])
    low_threshold = rng.choice([50, 60, 80])
    while low_threshold >= high_threshold:
        low_threshold = rng.choice([50, 60, 80])
    high_mult = rng.choice([1.5, 1.8, 2.0])
    low_mult = rng.choice([1.2, 1.3, 1.5])
    data["Q6"] = {
        "high_threshold": high_threshold,
        "low_threshold": low_threshold,
        "high_mult": high_mult,
        "low_mult": low_mult,
    }
    # Generate 5 test cases
    def _q6_expected(base, kg, ht, lt, hm, lm):
        if kg >= ht:
            return base * hm
        elif kg >= lt:
            return base * lm
        else:
            return float(base)

    q6_base_vals = [rng.randint(800, 2000) for _ in range(5)]
    q6_kg_vals = [
        high_threshold + 20,
        high_threshold,
        low_threshold + 10,
        low_threshold - 10,
        0,
    ]
    q6_cases = []
    for b, k in zip(q6_base_vals, q6_kg_vals):
        exp = _q6_expected(b, k, high_threshold, low_threshold, high_mult, low_mult)
        q6_cases.append({"args": (b, k), "expected": exp})
    expected["Q6_cases"] = q6_cases

    # --- Q7: List operations (read code) ---
    rng = _make_rng(student_id, "Q7")
    all_items = ["肥料", "種子", "農藥", "水管", "鋤頭", "手套", "鏟子", "噴霧器", "溫度計", "剪刀"]
    items = rng.sample(all_items, 5)
    remaining = [x for x in all_items if x not in items]
    new_item = rng.choice(remaining)
    insert_pos = rng.randint(1, 3)
    remove_idx = rng.randint(0, len(items) - 1)
    remove_item = items[remove_idx]
    data["Q7"] = {
        "inventory": list(items),  # copy
        "new_item": new_item,
        "insert_pos": insert_pos,
        "remove_item": remove_item,
    }
    # Simulate: insert -> remove -> append -> sorted -> [:3]
    sim = list(items)
    sim.insert(insert_pos, new_item)
    sim.remove(remove_item)
    sim_sorted = sorted(sim)
    expected["Q7"] = sim_sorted[:3]

    return data, expected


def _show_data_table(data):
    """Display all question data as HTML after init()."""
    rows = [
        f"Q1: total_harvest = {data['Q1']['total_harvest']}, num_fields = {data['Q1']['num_fields']}",
        f"Q2: raw_report = \"{data['Q2']['raw_report']}\"",
        f"Q3: 分級門檻 -- 優：{data['Q3']['excellent']}, 良：{data['Q3']['good']}, 可：{data['Q3']['fair']}",
        f"Q4: temperatures = {data['Q4']['temperatures']}, threshold = {data['Q4']['threshold']}",
        f"Q5: crops = {data['Q5']['crops']}, yields = {data['Q5']['yields']}",
        f"Q6: high_threshold = {data['Q6']['high_threshold']}, low_threshold = {data['Q6']['low_threshold']}, high_mult = {data['Q6']['high_mult']}, low_mult = {data['Q6']['low_mult']}",
        f"Q7: inventory = {data['Q7']['inventory']}, new_item = \"{data['Q7']['new_item']}\", insert_pos = {data['Q7']['insert_pos']}, remove_item = \"{data['Q7']['remove_item']}\"",
    ]
    items_html = "".join(f"<li><code>{r}</code></li>" for r in rows)
    html = f"""
    <div style="padding:16px;border-radius:8px;background:#e8f4fd;border:1px solid #bee5eb;margin:8px 0;">
        <strong style="color:#0c5460;">你的考試資料已產生：</strong>
        <ul style="margin:8px 0 0 0;padding-left:20px;color:#0c5460;font-family:monospace;">{items_html}</ul>
    </div>
    """
    _display_html(html)


def init(student_id):
    """Initialize exam: validate student, generate questions, display data."""
    global _student_id, _data, _expected, _results, _attempts, _scores
    _student_id = str(student_id).strip()
    if not _student_id or _student_id == "在這裡輸入你的學號":
        print("請輸入你的學號！例如：midterm.init('M1234567')")
        return

    result = init_tracking(_student_id, context="exam")
    if result == "blocked":
        return
    if result == "not_in_roster":
        print(f"學號 {_student_id} 不在名單中，請確認是否輸入正確。")
    if result == "unverified":
        # Warning already displayed by init_tracking; continue with exam
        pass

    _results = {}
    _attempts = {q: 0 for q in _Q_LABELS}
    _scores = {}
    _data, _expected = _generate_all(_student_id)

    print(f"學號 {_student_id} 已登記。")
    _show_data_table(_data)


def check_q1(answer):
    """Q1: Arithmetic -- student predicts result of // operation."""
    if is_blocked():
        return
    qid = "Q1"
    label = _Q_LABELS[qid]
    _attempts[qid] = _attempts.get(qid, 0) + 1
    try:
        passed = answer == _expected["Q1"]
        if passed:
            show_exam_result(label, True)
            _results[qid] = True
        else:
            show_exam_result(label, False)
            _results[qid] = False
        log_attempt(_student_id, _WEEK, qid, _results[qid],
                    submitted_code=repr(answer))
    except Exception as e:
        show_error(label, str(e))
        _results[qid] = False


def check_q2(answer):
    """Q2: String ops -- student predicts result of strip().split()."""
    if is_blocked():
        return
    qid = "Q2"
    label = _Q_LABELS[qid]
    _attempts[qid] = _attempts.get(qid, 0) + 1
    try:
        passed = answer == _expected["Q2"]
        if passed:
            show_exam_result(label, True)
            _results[qid] = True
        else:
            show_exam_result(label, False)
            _results[qid] = False
        log_attempt(_student_id, _WEEK, qid, _results[qid],
                    submitted_code=repr(answer))
    except Exception as e:
        show_error(label, str(e))
        _results[qid] = False


def check_q3(fn):
    """Q3: Conditionals -- fill-in-the-blank function, partial scoring."""
    if is_blocked():
        return
    qid = "Q3"
    label = _Q_LABELS[qid]
    _attempts[qid] = _attempts.get(qid, 0) + 1
    try:
        cases = _expected["Q3_cases"]
        passed_count = 0
        for score_val, expected_grade in cases:
            try:
                result = fn(score_val)
                if result == expected_grade:
                    passed_count += 1
            except Exception:
                pass
        total = len(cases)
        all_passed = passed_count == total
        _scores[qid] = (passed_count, total)
        if all_passed:
            show_exam_result(label, True)
            _results[qid] = True
        else:
            show_exam_result(label, False, score_ratio=(passed_count, total))
            _results[qid] = False
        log_attempt(_student_id, _WEEK, qid, all_passed,
                    submitted_code=_capture_source(fn))
    except Exception as e:
        show_error(label, str(e))
        _results[qid] = False


def check_q4(answer):
    """Q4: for loop -- student predicts (count, total) tuple."""
    if is_blocked():
        return
    qid = "Q4"
    label = _Q_LABELS[qid]
    _attempts[qid] = _attempts.get(qid, 0) + 1
    try:
        exp = _expected["Q4"]
        passed = False
        if isinstance(answer, tuple) and len(answer) == 2:
            passed = answer[0] == exp[0] and answer[1] == exp[1]
        if passed:
            show_exam_result(label, True)
            _results[qid] = True
        else:
            show_exam_result(label, False)
            _results[qid] = False
        log_attempt(_student_id, _WEEK, qid, _results[qid],
                    submitted_code=repr(answer))
    except Exception as e:
        show_error(label, str(e))
        _results[qid] = False


def check_q5(answer):
    """Q5: enumerate/zip -- student predicts the crop name with max yield."""
    if is_blocked():
        return
    qid = "Q5"
    label = _Q_LABELS[qid]
    _attempts[qid] = _attempts.get(qid, 0) + 1
    try:
        passed = answer == _expected["Q5"]
        if passed:
            show_exam_result(label, True)
            _results[qid] = True
        else:
            show_exam_result(label, False)
            _results[qid] = False
        log_attempt(_student_id, _WEEK, qid, _results[qid],
                    submitted_code=repr(answer))
    except Exception as e:
        show_error(label, str(e))
        _results[qid] = False


def check_q6(fn):
    """Q6: Function definition -- write function, partial scoring."""
    if is_blocked():
        return
    qid = "Q6"
    label = _Q_LABELS[qid]
    _attempts[qid] = _attempts.get(qid, 0) + 1
    try:
        cases = _expected["Q6_cases"]
        passed_count = 0
        for tc in cases:
            try:
                result = fn(*tc["args"])
                expected_val = tc["expected"]
                if isinstance(expected_val, float) and isinstance(result, (int, float)):
                    if abs(result - expected_val) < 1e-9:
                        passed_count += 1
                elif result == expected_val:
                    passed_count += 1
            except Exception:
                pass
        total = len(cases)
        all_passed = passed_count == total
        _scores[qid] = (passed_count, total)
        if all_passed:
            show_exam_result(label, True)
            _results[qid] = True
        else:
            show_exam_result(label, False, score_ratio=(passed_count, total))
            _results[qid] = False
        log_attempt(_student_id, _WEEK, qid, all_passed,
                    submitted_code=_capture_source(fn))
    except Exception as e:
        show_error(label, str(e))
        _results[qid] = False


def check_q7(answer):
    """Q7: List operations -- student predicts final list after operations."""
    if is_blocked():
        return
    qid = "Q7"
    label = _Q_LABELS[qid]
    _attempts[qid] = _attempts.get(qid, 0) + 1
    try:
        passed = answer == _expected["Q7"]
        if passed:
            show_exam_result(label, True)
            _results[qid] = True
        else:
            show_exam_result(label, False)
            _results[qid] = False
        log_attempt(_student_id, _WEEK, qid, _results[qid],
                    submitted_code=repr(answer))
    except Exception as e:
        show_error(label, str(e))
        _results[qid] = False


def check_all():
    """Show exam summary with scores."""
    if not _results:
        print("你還沒有檢查任何題目。")
        return

    total_score = 0
    max_score = sum(_Q_POINTS.values())

    rows_html = ""
    for qid in ["Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "Q7"]:
        label = _Q_LABELS[qid]
        pts = _Q_POINTS[qid]
        if qid not in _results:
            mark = "--"
            earned = 0
        elif _results[qid]:
            mark = "PASS"
            earned = pts
        elif qid in _scores:
            p, t = _scores[qid]
            ratio = p / t if t > 0 else 0
            earned = round(pts * ratio)
            mark = f"{p}/{t}"
        else:
            mark = "FAIL"
            earned = 0
        total_score += earned
        rows_html += f"<tr><td style='padding:4px 12px;'>{label}</td><td style='padding:4px 12px;text-align:center;'>{mark}</td><td style='padding:4px 12px;text-align:center;'>{earned}/{pts}</td></tr>"

    html = f"""
    <div style="padding:16px;border-radius:8px;background:#e2e3e5;border:1px solid #d6d8db;margin:8px 0;">
        <strong style="color:#383d41;">期中考結果</strong>
        <table style="border-collapse:collapse;color:#383d41;margin-top:8px;">
            <tr><th style="padding:4px 12px;text-align:left;">題目</th><th style="padding:4px 12px;">狀態</th><th style="padding:4px 12px;">得分</th></tr>
            {rows_html}
        </table>
        <div style="margin-top:8px;font-weight:bold;color:#383d41;">總分：{total_score} / {max_score}</div>
    </div>
    """
    _display_html(html)
