"""Google Sheets 追蹤 + 身分驗證 + 證據收集

老師集中追蹤架構：
- 使用老師建立的共用 Google Sheet（by key）
- 透過 OAuth 取得學生 Google email
- 從 roster 分頁驗證學號 + Gmail 綁定
- 收集環境資訊、程式碼作為證據
- 不匹配時阻擋答題並記錄到 security worksheet
"""

import datetime
import sys
import platform

SHEET_KEY = "1uZ86VnrAXRuIy9Mcw2sytBajsOeA5rKKriB8lG0NxYM"

_sheet = None
_enabled = False
_google_email = None
_roster_valid = None  # None=未驗證, True=在名單中, False=不在名單中

# --- 新增：身分驗證與阻擋狀態 ---
_student_id = None
_init_timestamp = None
_auth_status = None    # "verified" / "unverified" / "failed"
_blocked = False
_context = None        # "homework" / "exam"
_spreadsheet = None    # gspread Spreadsheet object


def is_blocked():
    """公開函式，供各模組檢查是否被阻擋"""
    return _blocked


def _collect_env_info():
    """收集 Python 版本、OS、是否 Colab、IP"""
    is_colab = "google.colab" in sys.modules
    ip = ""
    try:
        import urllib.request
        with urllib.request.urlopen("https://api.ipify.org", timeout=3) as resp:
            ip = resp.read().decode().strip()
    except Exception:
        pass
    return (
        f"Python {sys.version.split()[0]} | "
        f"{platform.system()} {platform.release()} | "
        f"Colab={is_colab} | "
        f"IP={ip}"
    )


def _capture_source(obj):
    """擷取 callable 原始碼（inspect.getsource + IPython fallback）

    Returns
    -------
    str
        原始碼字串，失敗時回傳 repr(obj)
    """
    if not callable(obj):
        return repr(obj)
    try:
        import inspect
        return inspect.getsource(obj)
    except (OSError, TypeError):
        pass
    # IPython / Colab fallback: 從 cell history 取得
    try:
        ip = get_ipython()  # noqa: F821
        for cell in reversed(ip.user_ns.get("In", [])):
            if cell and obj.__name__ in cell:
                return cell
    except Exception:
        pass
    return repr(obj)


def _display_block_error(student_id, reason, detail):
    """顯示紅色 HTML 錯誤阻擋框"""
    html = f"""
    <div style="padding:16px;border-radius:8px;background:#f8d7da;border:2px solid #dc3545;margin:8px 0;">
        <div style="font-size:20px;font-weight:bold;color:#721c24;">⛔ {reason}</div>
        <div style="color:#721c24;margin-top:8px;">{detail}</div>
        <div style="color:#721c24;margin-top:8px;font-size:0.85em;">
            學號：{student_id} | 時間：{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
        </div>
    </div>
    """
    try:
        from IPython.display import display, HTML
        display(HTML(html))
    except ImportError:
        import re
        print(re.sub(r"<[^>]+>", "", html))


def _display_warning(message):
    """顯示黃色警告框"""
    html = f"""
    <div style="padding:12px 16px;border-radius:8px;background:#fff3cd;border:1px solid #ffeeba;margin:8px 0;">
        <span style="font-size:18px;">⚠️</span>
        <strong style="color:#856404;margin-left:8px;">{message}</strong>
    </div>
    """
    try:
        from IPython.display import display, HTML
        display(HTML(html))
    except ImportError:
        import re
        print(re.sub(r"<[^>]+>", "", html))


def _mask_email(email):
    """遮蔽 email 前半段：abc@gmail.com → ab***@gmail.com"""
    if not email or "@" not in email:
        return email or ""
    local, domain = email.split("@", 1)
    if len(local) <= 2:
        return f"{local[0]}***@{domain}"
    return f"{local[:2]}***@{domain}"


def _bind_or_verify_email(roster_ws, student_id, email):
    """首次綁定或驗證 Gmail

    Returns
    -------
    str
        "bound" = 首次綁定成功
        "verified" = Email 匹配
        "mismatch" = Email 不匹配
    str or None
        綁定的 email（不匹配時回傳已綁定的 email）
    """
    try:
        roster_ids = [str(cell).strip() for cell in roster_ws.col_values(1)]
        if student_id not in roster_ids:
            return "not_in_roster", None
        row_idx = roster_ids.index(student_id) + 1  # 1-based

        # 讀取已綁定的 email（column B）
        try:
            bound_email = roster_ws.cell(row_idx, 2).value
        except Exception:
            bound_email = None

        if not bound_email or not bound_email.strip():
            # 首次綁定：寫入 email + timestamp
            now = datetime.datetime.now().isoformat()
            roster_ws.update_cell(row_idx, 2, email)
            roster_ws.update_cell(row_idx, 3, now)
            return "bound", email
        else:
            bound_email = bound_email.strip()
            if bound_email.lower() == email.lower():
                return "verified", bound_email
            else:
                return "mismatch", bound_email
    except Exception:
        # roster 操作失敗，不阻擋
        return "error", None


def _get_or_create_security_ws(sp):
    """取得或建立 security worksheet"""
    try:
        return sp.worksheet("security")
    except Exception:
        try:
            ws = sp.add_worksheet(title="security", rows=1000, cols=6)
            ws.append_row([
                "student_id", "expected_email", "actual_email",
                "timestamp", "env_info", "context",
            ])
            return ws
        except Exception:
            return None


def _log_impersonation(student_id, expected_email, actual_email):
    """記錄冒名嘗試到 security worksheet"""
    global _spreadsheet
    if _spreadsheet is None:
        return
    try:
        sec_ws = _get_or_create_security_ws(_spreadsheet)
        if sec_ws is None:
            return
        sec_ws.append_row([
            str(student_id),
            str(expected_email or ""),
            str(actual_email or ""),
            datetime.datetime.now().isoformat(),
            _collect_env_info(),
            _context or "",
        ])
    except Exception:
        pass


def _log_env_info(student_id, context):
    """記錄 START + 環境資訊到主日誌"""
    if not _enabled or _sheet is None:
        return
    try:
        env_info = _collect_env_info()
        _sheet.append_row([
            str(student_id),
            context or "",
            "START",
            "TRUE",
            _google_email or "",
            datetime.datetime.now().isoformat(),
            _auth_status or "",
            env_info,
        ])
    except Exception:
        pass


def init_tracking(student_id, context="homework"):
    """嘗試連接 Google Sheets，驗證學號 + Gmail 綁定，取得 email。

    Parameters
    ----------
    student_id : str
        學生學號
    context : str
        "homework" 或 "exam"

    Returns
    -------
    str or None
        "ok" = 學號驗證通過，email 匹配或首次綁定
        "not_in_roster" = 學號不在名單中
        "blocked" = Gmail 不匹配，已阻擋
        "unverified" = OAuth 失敗但允許繼續（exam mode）
        None = 追蹤功能無法使用
    """
    global _sheet, _enabled, _google_email, _roster_valid
    global _student_id, _init_timestamp, _auth_status, _blocked, _context, _spreadsheet

    _student_id = str(student_id).strip()
    _init_timestamp = datetime.datetime.now().isoformat()
    _context = context
    _blocked = False

    try:
        from google.colab import auth
        auth.authenticate_user()
        import gspread
        from google.auth import default
        import urllib.request
        import json

        creds, _ = default()
        gc = gspread.authorize(creds)
        sp = gc.open_by_key(SHEET_KEY)
        _sheet = sp.sheet1
        _spreadsheet = sp
        _enabled = True

        # 取得 Google email（先 refresh 確保 token 存在）
        try:
            import google.auth.transport.requests
            request = google.auth.transport.requests.Request()
            creds.refresh(request)
            token = creds.token
            req = urllib.request.Request(
                "https://www.googleapis.com/oauth2/v3/userinfo",
                headers={"Authorization": f"Bearer {token}"},
            )
            with urllib.request.urlopen(req) as resp:
                _google_email = json.loads(resp.read().decode())["email"]
        except Exception:
            _google_email = None

        if _google_email is None:
            # OAuth 成功但無法取得 email
            if context == "homework":
                _auth_status = "failed"
                _blocked = True
                _display_block_error(
                    _student_id,
                    "身分驗證失敗",
                    "追蹤系統連線失敗。作業模式需要 Google 驗證。",
                )
                return "blocked"
            else:
                _auth_status = "unverified"
                _display_warning(
                    "注意：Google 驗證未完成，本次考試將標記為「未驗證」。"
                )

        # 驗證學號 + Gmail 綁定
        try:
            roster_ws = sp.worksheet("roster")
            roster_ids = [
                str(cell).strip()
                for cell in roster_ws.col_values(1)
                if str(cell).strip()
            ]
            if _student_id not in roster_ids:
                _roster_valid = False
                _auth_status = _auth_status or "verified"
                _log_env_info(_student_id, context)
                return "not_in_roster"

            _roster_valid = True

            if _google_email:
                bind_result, bound_email = _bind_or_verify_email(
                    roster_ws, _student_id, _google_email
                )
                if bind_result == "mismatch":
                    _blocked = True
                    _auth_status = "failed"
                    masked = _mask_email(bound_email)
                    _log_impersonation(_student_id, bound_email, _google_email)
                    _display_block_error(
                        _student_id,
                        "⚠ 身分驗證失敗 ⚠",
                        f"Gmail 帳號與綁定不符。此學號已綁定 {masked}，"
                        f"但目前登入的是 {_google_email}",
                    )
                    return "blocked"
                elif bind_result in ("bound", "verified"):
                    _auth_status = "verified"
                # bind_result == "error" → 不阻擋
                elif bind_result == "error":
                    _auth_status = _auth_status or "verified"
            else:
                # email 為 None 但已處理過（exam unverified 模式）
                _auth_status = _auth_status or "unverified"

            _log_env_info(_student_id, context)
            return "ok"

        except Exception:
            # roster 分頁不存在或讀取失敗，不阻擋
            _roster_valid = None
            _auth_status = _auth_status or "verified"
            _log_env_info(_student_id, context)
            return "ok"

    except Exception:
        # OAuth 或 Sheets 整體失敗
        _enabled = False
        if context == "homework":
            _auth_status = "failed"
            _blocked = True
            _display_block_error(
                _student_id,
                "身分驗證失敗",
                "追蹤系統連線失敗。作業模式需要 Google 驗證。",
            )
            return "blocked"
        else:
            _auth_status = "unverified"
            _display_warning(
                "注意：Google 驗證未完成，本次考試將標記為「未驗證」。"
            )
            return "unverified"


def log_attempt(student_id, week, question_id, passed, submitted_code=""):
    """記錄一筆嘗試，失敗就靜默跳過

    Parameters
    ----------
    student_id : str
    week : str
    question_id : str
    passed : bool
    submitted_code : str
        學生提交的程式碼或答案（repr 或原始碼）
    """
    if _blocked:
        return
    if not _enabled or _sheet is None:
        return
    try:
        # 截斷過長的 submitted_code（Google Sheets 單格上限 50000 字元）
        code_str = str(submitted_code)[:5000] if submitted_code else ""
        _sheet.append_row([
            str(student_id),
            str(week),
            str(question_id),
            "PASS" if passed else "FAIL",
            _google_email or "",
            datetime.datetime.now().isoformat(),
            _auth_status or "",
            code_str,
        ])
    except Exception:
        pass
