#!/usr/bin/env python3
"""
CodeHouse Cloud — Terminal CLI (rich)
Single-file runnable client.

Save as: codehouse_cli.py
Requires: pip install rich requests
Configure: set API_BASE_URL below
"""

from __future__ import annotations
import sys, os, json, time, uuid
from typing import Optional, Dict, Any, Callable
from dataclasses import dataclass
import requests
from requests import RequestException

# Rich imports
from rich import box, print as rprint
from rich.console import Console
from rich.panel import Panel
from rich.align import Align
from rich.prompt import Prompt, Confirm
from rich.table import Table
from rich.live import Live
from rich.text import Text
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.layout import Layout
from rich.columns import Columns

console = Console()

# =========================
# ASCII LOGO (cyan-blue)
# =========================
LOGO = r"""
 ▄████▄   ▒█████  ▓█████▄ ▓█████  ██░ ██  ▒█████   █    ██  ███████ ▓█████ 
▒██▀ ▀█  ▒██▒  ██▒▒██▀ ██▌▓█   ▀ ▓██░ ██▒▒██▒  ██▒ ██  ▓██▒██    ▒ ▓█   ▀ 
▒▓█    ▄ ▒██░  ██▒░██   █▌▒███   ▒██▀▀██░▒██░  ██▒▓██  ▒██░ ▓██▄   ▒███   
▒▓▓▄ ▄██▒▒██   ██░░▓█▄   ▌▒▓█  ▄ ░▓█ ░██ ▒██   ██░▓▓█  ░██░ ▒   ██▒▒▓█  ▄ 
▒ ▓███▀ ░░ ████▓▒░░▒████▓ ░▒████▒░▓█▒░██▓░ ████▓▒░▒▒█████▓ ▒██████▒▒░▒████▒
░ ░▒ ▒  ░░ ▒░▒░▒░  ▒▒▓  ▒ ░░ ▒░ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░▒▓▒ ▒ ▒ ▒ ▒▓▒ ▒ ░░░ ▒░ ░
  ░  ▒     ░ ▒ ▒░  ░ ▒  ▒  ░ ░  ░ ▒ ░▒░ ░  ░ ▒ ▒░ ░░▒░ ░ ░ ░ ░▒  ░ ░ ░ ░  ░
░        ░ ░ ░ ▒   ░ ░  ░    ░    ░  ░░ ░░ ░ ░ ▒   ░░░ ░ ░ ░  ░  ░     ░   
░ ░          ░ ░     ░       ░  ░ ░  ░  ░    ░ ░     ░           ░     ░  ░
             ░         ░                                                  

                              CLOUD
"""

# =========================
# Configuration
# =========================
API_BASE_URL = os.environ.get("CODEHOUSE_API_URL", "https://cli.codehouse.cloud/quiz/")  # <<-- set your API base URL here
SESSION_FILE = os.path.expanduser("~/.codehouse_session")

# endpoints mapping — match your Django urls
ENDPOINTS = {
    "register": "auth/register/",
    "verify_email": "auth/verify-email/",
    "login": "auth/login/",
    "recover_request": "recover-password/",            # maps to views.recover_password
    "verify_recovery": "verify-recovery-token/",       # maps to verify_recovery_token
    "reset_password": "reset-password/",               # maps to reset_password
    "courses": "courses/",
    "quiz_status": "quiz/{course_id}/status/",
    "quiz_question": "quiz/{course_id}/question/{q_index}/",
    "submit_answer": "submit-answer/{course_id}/",
    "leaderboard": "leaderboard/",
    "wallet": "wallet/",
    "withdraw": "withdraw/",
    "top_scorers": "quiz/{course_id}/top/",
    # optional: finish, result_pdf if present on backend
    "finish": "quiz/{course_id}/finish/",
    "result_pdf": "results/{result_id}/pdf/",
    "appeal_block": "auth/appeal/"

}

# network timeout & retry config
REMOTE_TIMEOUT = 15
DEFAULT_RETRIES = 2

# =========================
# Session Manager
# =========================
def load_session() -> Dict[str, Any]:
    if os.path.exists(SESSION_FILE):
        try:
            with open(SESSION_FILE, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            return {}
    return {}

def save_session(obj: Dict[str, Any]):
    try:
        with open(SESSION_FILE, "w", encoding="utf-8") as f:
            json.dump(obj, f)
    except Exception as e:
        console.log(f"[yellow]Warning saving session:[/yellow] {e}")

# =========================
# Network Helper (wrapper)
# =========================
class NetworkHelper:
    def __init__(self, console: Console, retries: int = DEFAULT_RETRIES):
        self.console = console
        self.retries = retries

    def request(self, func: Callable[[], requests.Response], spinner_text: Optional[str] = None) -> requests.Response:
        """Execute func() which should return a requests.Response. Retries on transient errors."""
        last_exc = None
        for attempt in range(max(1, self.retries)):
            try:
                # show spinner for each attempt
                if spinner_text:
                    with console.status(f"[cyan]{spinner_text}[/cyan]", spinner="dots"):
                        r = func()
                else:
                    r = func()
                return r
            except RequestException as exc:
                last_exc = exc
                console.print(f"[yellow]Network error (attempt {attempt+1}/{self.retries}):[/yellow] {exc}")
                time.sleep(0.6 + attempt * 0.5)
                continue
        # final attempt outside spinner
        if last_exc:
            raise last_exc
        raise RuntimeError("NetworkHelper.request failed without exception")

# =========================
# API Client
# =========================
class APIClient:
    def __init__(self, base_url: Optional[str] = None, endpoints: Optional[Dict[str, str]] = None):
        self.base_url = (base_url or API_BASE_URL).rstrip("/") + "/"
        self.endpoints = endpoints or ENDPOINTS
        self.session = load_session()
        self.token = self.session.get("token")
        self.sti = self.session.get("sti")
        self.net = NetworkHelper(console)

    def _url(self, key: str, **kwargs) -> str:
        ep = self.endpoints.get(key)
        if not ep:
            raise KeyError(f"Unknown endpoint {key}")
        return self.base_url + ep.format(**kwargs)

    def _headers(self) -> Dict[str, str]:
        headers = {"Content-Type": "application/json"}
        if self.sti and self.token:
            headers.update({"STI": str(self.sti), "Authorization": str(self.token)})
        return headers

    # Auth endpoints
    def register(self, fullname, email, password, phone, country):
        return self.net.request(lambda: requests.post(self._url("register"),
                                                     json={"fullname": fullname, "email": email, "password": password, "phone": phone, "country": country},
                                                     timeout=REMOTE_TIMEOUT),
                                spinner_text="Registering")

    def verify_email(self, email, token):
        return self.net.request(lambda: requests.post(self._url("verify_email"),
                                                     json={"email": email, "token": token},
                                                     timeout=REMOTE_TIMEOUT),
                                spinner_text="Verifying email")

    def login(self, sti, password):
        return self.net.request(lambda: requests.post(self._url("login"),
                                                     json={"sti": sti, "password": password},
                                                     timeout=REMOTE_TIMEOUT),
                                spinner_text="Logging in")

    # Recovery
    def request_recovery(self, sti):
        return self.net.request(lambda: requests.post(self._url("recover_request"),
                                                     json={"sti": sti}, timeout=REMOTE_TIMEOUT),
                                spinner_text="Requesting recovery token")

    def verify_recovery(self, sti, recovery_token):
        return self.net.request(lambda: requests.post(self._url("verify_recovery"),
                                                     json={"sti": sti, "recovery_token": recovery_token}, timeout=REMOTE_TIMEOUT),
                                spinner_text="Verifying recovery token")

    def reset_password(self, sti, token, new_password):
        return self.net.request(lambda: requests.post(self._url("reset_password"),
                                                     json={"sti": sti, "token": token, "new_password": new_password}, timeout=REMOTE_TIMEOUT),
                                spinner_text="Resetting password")

    # Courses / quiz
    def get_courses(self):
        return self.net.request(lambda: requests.get(self._url("courses"), headers=self._headers(), timeout=REMOTE_TIMEOUT),
                                spinner_text="Fetching courses")

    def get_status(self, course_id):
        return self.net.request(lambda: requests.get(self._url("quiz_status", course_id=course_id), headers=self._headers(), timeout=REMOTE_TIMEOUT),
                                spinner_text="Checking quiz status")

    def get_question(self, course_id, q_index):
        return self.net.request(lambda: requests.get(self._url("quiz_question", course_id=course_id, q_index=q_index), headers=self._headers(), timeout=REMOTE_TIMEOUT),
                                spinner_text="Loading question")

    def submit_answer(self, course_id, q_index, selected, time_spent, source="cli"):
        return self.net.request(lambda: requests.post(self._url("submit_answer", course_id=course_id),
                                                     headers=self._headers(),
                                                     json={"q_index": q_index, "answer": selected, "time_spent": time_spent, "source": source},
                                                     timeout=REMOTE_TIMEOUT),
                                spinner_text="Submitting answer")

    def get_top(self, course_id):
        return self.net.request(lambda: requests.get(self._url("top_scorers", course_id=course_id), headers=self._headers(), timeout=REMOTE_TIMEOUT),
                                spinner_text="Fetching top scorers")

    # def finish(self, course_id):
    #     # optional endpoint - may not exist on server. If not, server will handle finalize on submit of last question.
    #     try:
    #         return self.net.request(lambda: requests.post(self._url("finish", course_id=course_id), headers=self._headers(), timeout=REMOTE_TIMEOUT),
    #                                 spinner_text="Finalizing attempt")
    #     except Exception:
    #         # fallback to a dummy response
    #         class R: status_code = 200
    #         def json(self): return {}
    #         return R()
    def finish(self, course_id):
        try:
            return self.net.request(lambda: requests.post(
                self._url("finish", course_id=course_id),
                headers=self._headers(), timeout=REMOTE_TIMEOUT),
                spinner_text="Finalizing attempt")
        except Exception:
            class R: status_code = 200
            def json(self): return {}
            return R()

    def get_top(self, course_id):
        return self.net.request(lambda: requests.get(
            self._url("top_scorers", course_id=course_id),
            headers=self._headers(), timeout=REMOTE_TIMEOUT
        ), spinner_text="Loading leaderboard")

    def finish(self, course_id):
        return self.net.request(lambda: requests.post(
            self._url("finish", course_id=course_id),
            headers=self._headers(), timeout=REMOTE_TIMEOUT
        ), spinner_text="Finalizing quiz")

    def get_result_pdf(self, result_id):
        return self.net.request(lambda: requests.get(
            self._url("result_pdf", result_id=result_id),
            headers=self._headers(), timeout=REMOTE_TIMEOUT
        ), spinner_text="Preparing your result PDF")

    # Wallet & withdraw
    def get_wallet(self):
        return self.net.request(lambda: requests.get(self._url("wallet"), headers=self._headers(), timeout=REMOTE_TIMEOUT),
                                spinner_text="Fetching wallet")

    def request_withdraw(self, quizcoin_amount: float, ngn_rate: Optional[float] = None, bank_name: Optional[str] = None, account_number: Optional[str] = None):
        body = {"quizcoin_amount": quizcoin_amount}
        if ngn_rate is not None:
            body['ngn_rate'] = ngn_rate
        if bank_name:
            body['bank_name'] = bank_name
        if account_number:
            body['account_number'] = account_number
        return self.net.request(lambda: requests.post(self._url("withdraw"), headers=self._headers(), json=body, timeout=REMOTE_TIMEOUT),
                                spinner_text="Requesting withdrawal")



    def appeal_block(self):
        return self.net.request(lambda: requests.post(
            self._url("appeal_block"),
            headers=self._headers(), timeout=REMOTE_TIMEOUT
        ), spinner_text="Submitting appeal")

# =========================
# Helper utilities
# =========================
def pretty_time(seconds: int) -> str:
    if seconds is None:
        return "0s"
    m, s = divmod(int(seconds), 60)
    h, m = divmod(m, 60)
    parts = []
    if h: parts.append(f"{h}h")
    if m: parts.append(f"{m}m")
    parts.append(f"{s}s")
    return " ".join(parts)

# =========================
# Main CLI class (Rich-driven)
# =========================
class QuizCLI:
    def __init__(self, api: APIClient):
        self.api = api
        self.session = load_session()
        self.start_time = None

    # Startup banner with typing effect
    def display_banner(self):
        console.clear()
        console.rule("[bold cyan]🚀 Starting CODEHOUSE CLOUD[/bold cyan]", style="cyan")
        console.print()
        # typing effect
        for ch in LOGO:
            console.print(ch, end="")
            time.sleep(0.0015)
        console.print("\n")
        panel = Panel(Align.center(Text("CODEHOUSE CLOUD", style="bold cyan")),
                      box=box.HEAVY, border_style="cyan",
                      padding=(1, 3),
                      title="[bright_white]Applinet Technology[/bright_white]",
                      subtitle="[dim]Empowering Africans through technology[/dim]")
        console.print(panel)
        console.print()

    # ---------- flows ----------
    def register_flow(self):
        console.print(Panel.fit("[bold yellow]Create Account[/bold yellow]"))
        fullname = Prompt.ask("Full name")
        email = Prompt.ask("Email")
        password = Prompt.ask("Password", password=True)
        if len(password) < 6:
            console.print("[red]Password must be at least 6 characters[/red]")
            return
        confirm_pw = Prompt.ask("Confirm password", password=True)
        if password != confirm_pw:
            console.print("[red]Passwords do not match[/red]")
            return
        phone = Prompt.ask("Phone (e.g. +234...)", default="")
        country = Prompt.ask("Country code (e.g. nga, usa)", default="nga")
        r = self.api.register(fullname, email, password, phone, country)
        if r.status_code in (200, 201):
            console.print(Panel.fit("[green]Registration OK — check your email for confirmation token[/green]"))
            token = Prompt.ask("Enter confirmation token (from email)")
            vr = self.api.verify_email(email, token)
            if vr.status_code in (200, 201):
                body = vr.json()
                sti = body.get("sti")
                console.print(Panel.fit(f"[green]Email verified! Your STI: [bold]{sti}[/bold][/green]"))
                # save minimal session (no token yet)
                self.session.update({"email": email, "sti": sti})
                save_session(self.session)
            else:
                console.print(Panel.fit(f"[red]Verify failed: {vr.status_code} {getattr(vr, 'text', '')}[/red]"))
        else:
            try:
                msg = r.json()
            except Exception:
                msg = r.text
            console.print(Panel.fit(f"[red]Registration failed: {msg}[/red]"))

    def login_flow(self, prefill_sti: Optional[str] = None):
        console.print(Panel.fit("[bold yellow]Login[/bold yellow]"))
        sti = prefill_sti or Prompt.ask("Enter STI")
        password = Prompt.ask("Password", password=True)
        try:
            r = self.api.login(sti, password)
        except Exception as e:
            console.print(Panel.fit(f"[red]Network/login error: {e}[/red]"))
            return
        if r.status_code == 200:
            body = r.json()
            token = body.get("token")
            sti_resp = body.get("sti") or sti
            # Save session locally
            self.api.token = token
            self.api.sti = sti_resp
            self.session.update({"sti": sti_resp, "token": token})
            save_session(self.session)
            console.print(Panel.fit("[green]Login successful![/green]"))
            # create login history handled on server (server will enrich location)
            self.after_login()
        else:
            try:
                body = r.json()
                console.print(Panel.fit(f"[red]Login failed: {body.get('detail') or body}[/red]"))
            except Exception:
                console.print(Panel.fit(f"[red]Login failed: status {r.status_code}[/red]"))

    def password_recovery_flow(self):
        console.print(Panel.fit("[bold yellow]🔑 Password / STI Recovery[/bold yellow]"))
        sti = Prompt.ask("Enter your STI (or leave blank if unknown)", default="")
        if not sti:
            console.print("[yellow]You must provide STI to request recovery. If you don't have STI, contact support.[/yellow]")
            return
        r = self.api.request_recovery(sti)
        if r.status_code in (200, 201):
            console.print(Panel.fit("[green]If your account exists, a recovery token was sent to your CodeHouse Cloud email.[/green]"))
            # proceed to verify
            token = Prompt.ask("Enter recovery token (from your CodeHouse Cloud email)")
            vr = self.api.verify_recovery(sti, token)
            if vr.status_code in (200, 201):
                console.print(Panel.fit("[green]Recovery token verified. Continue to reset password.[/green]"))
                new_pw = Prompt.ask("New password", password=True)
                confirm_pw = Prompt.ask("Confirm password", password=True)
                if new_pw != confirm_pw:
                    console.print(Panel.fit("[red]Passwords do not match[/red]"))
                    return
                rr = self.api.reset_password(sti, self.session.get("temp_token") or token, new_pw)
                if rr.status_code in (200, 201):
                    console.print(Panel.fit("[green]Password reset successful. Please login.[/green]"))
                else:
                    try:
                        msg = rr.json()
                    except Exception:
                        msg = rr.text
                    console.print(Panel.fit(f"[red]Reset failed: {msg}[/red]"))
            else:
                try:
                    msg = vr.json()
                except Exception:
                    msg = vr.text
                console.print(Panel.fit(f"[red]Verification failed: {msg}[/red]"))
        else:
            console.print(Panel.fit(f"[red]Recovery request failed: {r.status_code}[/red]"))

    def after_login(self):
        # fetch courses
        r = self.api.get_courses()
        if r.status_code != 200:
            console.print(Panel.fit(f"[red]Failed to fetch courses: {r.status_code}[/red]"))
            return
        courses = r.json()
        table = Table(title="Available Courses", header_style="bold cyan")
        table.add_column("#", justify="center")
        table.add_column("Course ID")
        table.add_column("Title")
        for idx, c in enumerate(courses, 1):
            table.add_row(str(idx), str(c.get("id")), c.get("title"))
        console.print(table)
        choice = Prompt.ask("Choose course number (or 'q' to quit)", default="1")
        if choice.lower() == "q":
            return
        try:
            selected = courses[int(choice)-1]
        except Exception:
            console.print(Panel.fit("[red]Invalid selection[/red]"))
            return
        self.session['selected_course'] = {'id': selected.get("id"), 'title': selected.get("title")}
        save_session(self.session)
        self.quiz_loop()

    def quiz_loop(self):

        course = self.session.get("selected_course")
        if not course:
            console.print(Panel.fit("[red]No course selected[/red]"))
            return
        course_id = course['id']

        # show leaderboard snapshot
        try:
            # 🏆 Show Top Scorers
            top_r = self.api.get_top(course_id)
            if top_r.status_code == 200:
                top = top_r.json().get("top_scorers", [])[:20]
                if top:
                    t = Table(title="🏆 Top Scorers", header_style="bold magenta")
                    t.add_column("#", justify="center")
                    t.add_column("Username", justify="left")
                    t.add_column("Score (%)", justify="center")

                    for i, s in enumerate(top, 1):
                        t.add_row(str(i), str(s.get("username", "---")), f"{s.get('percentage', 0)}%")

                    console.print(t)
                else:
                    console.print("[yellow]No leaderboard yet. Be the first to take the quiz![/yellow]")
            else:
                console.print("[red]Unable to fetch top scorers.[/red]")

            console.print("\n[cyan]Starting your quiz...[/cyan]\n")

            # top_r = self.api.get_top(course_id)
            # if top_r.status_code == 200:
            #     top = top_r.json()[:20]
            #     t = Table(title="Top 20 - Before Start", header_style="bold magenta")
            #     t.add_column("Rank"); t.add_column("Name/STI"); t.add_column("Score")
            #     for i, s in enumerate(top, 1):
            #         t.add_row(str(i), str(s.get('name') or s.get('sti') or '---'), str(s.get('score', '---')))
            #     console.print(t)
        except Exception:
            pass

        # get status
        status_r = self.api.get_status(course_id)
        if status_r.status_code != 200:
            console.print(Panel.fit("[red]Could not retrieve quiz status[/red]"))
            return
        status = status_r.json()
        current_q = status.get("current_q", 1)
        total_q = status.get("total_q", 10)

        last_rank = None
        while True:
            q_r = self.api.get_question(course_id, current_q)
            if q_r.status_code != 200:
                console.print(Panel.fit("[red]Error loading question[/red]"))
                return
            q = q_r.json()
            console.print(Panel.fit(Text(f"Question {current_q}/{total_q}\n\n{q.get('text')}", style="bold cyan"), title=course.get("title")))
            options = q.get("options", [])
            for i, opt in enumerate(options, 1):
                console.print(f"[cyan]{i}.[/cyan] {opt}")
            console.print("[yellow]Type option number. 't' to view top scorers, 'e' to exit quiz.[/yellow]")
            q_start = time.time()
            ans = Prompt.ask("Your answer")
            time_spent = int(time.time() - q_start)
            if ans.lower() in ("e", "end"):
                if Confirm.ask("Quit and save progress?"):
                    self.session['last_q'] = current_q
                    save_session(self.session)
                    console.print("[cyan]Progress saved. You can resume later.[/cyan]")
                    return
                else:
                    continue
            if ans.lower() in ("t", "top"):
                top_r = self.api.get_top(course_id)
                if top_r.status_code == 200:
                    top = top_r.json()[:20]
                    t = Table(title="Top 20 Right Now", header_style="bold magenta")
                    t.add_column("Rank"); t.add_column("Name/STI"); t.add_column("Score")
                    for i, s in enumerate(top, 1):
                        t.add_row(str(i), str(s.get('name') or s.get('sti') or '---'), str(s.get('score', '---')))
                    console.print(t)
                else:
                    console.print("[yellow]Top scorers unavailable.[/yellow]")
                continue
            if not ans.isdigit() or int(ans) < 1 or int(ans) > len(options):
                console.print("[red]Invalid option number[/red]")
                continue
            selected_text = options[int(ans)-1]
            sub_r = self.api.submit_answer(course_id, current_q, selected_text, time_spent, source="cli")
            if sub_r.status_code not in (200, 201):
                console.print(Panel.fit(f"[red]Submit failed: {sub_r.status_code}[/red]"))
                continue
            sub = sub_r.json()
            correct = sub.get("correct", False)
            console.print(Panel.fit(f"{'✅ Correct!' if correct else '❌ Incorrect!'}  Time: {pretty_time(time_spent)}", style="green" if correct else "red"))
            # update leaderboards if any
            try:
                top_r2 = self.api.get_top(course_id)
                if top_r2.status_code == 200:
                    top2 = top_r2.json()[:20]
                    my_sti = self.api.sti or self.session.get("sti")
                    my_rank = None
                    for i, s in enumerate(top2, 1):
                        if str(s.get("sti") or s.get("name") or "").lower() == str(my_sti or "").lower():
                            my_rank = i; break
                    if my_rank and my_rank != last_rank:
                        last_rank = my_rank
                        console.print(Panel.fit(f"[bold green]Leaderboard update: You are now ranked #{my_rank}[/bold green]"))
            except Exception:
                pass
            current_q = sub.get("next_q") or current_q + 1
            if sub.get("finished"):
                console.print(Panel.fit("[bold cyan]🏁 Quiz Finished! Fetching summary...[/bold cyan]"))
                fin = self.api.finish(course_id)
                result = fin.json() if getattr(fin, "json", None) else {}
                table = Table(title="Quiz Result Summary", header_style="bold green")
                table.add_column("Attempt"); table.add_column("Score (%)"); table.add_column("Time Spent")
                table.add_row(str(result.get("attempt_number", 1)), str(result.get("score", 0)), pretty_time(result.get("time_spent", 0)))
                console.print(table)
                # show final top
                try:
                    final_top = self.api.get_top(course_id)
                    if final_top.status_code == 200:
                        ft = final_top.json()[:20]
                        t2 = Table(title="Top 20 (Final)", header_style="bold magenta")
                        t2.add_column("Rank"); t2.add_column("Name/STI"); t2.add_column("Score")
                        for i, s in enumerate(ft,1):
                            t2.add_row(str(i), str(s.get("name") or s.get("sti") or '---'), str(s.get("score", '---')))
                        console.print(t2)
                except Exception:
                    pass


            fin = self.api.finish(course_id)
            result = fin.json() if getattr(fin, "json", None) else {}

            if fin.status_code == 200 and result:
                console.print(Panel.fit(f"[green]{result.get('message', 'Quiz completed successfully!')}[/green]"))

                # Show result summary
                table = Table(title="Result Summary", header_style="bold green")
                table.add_column("Score (%)", justify="center")
                table.add_column("QuizCoin Earned", justify="center")
                table.add_column("Wallet Balance", justify="center")
                table.add_row(
                    str(result.get("percentage", 0)),
                    str(result.get("quizcoin_earned", 0)),
                    str(result.get("wallet_balance", 0))
                )
                console.print(table)

                # 📧 Fetch and email result PDF
                if "result_id" in result:
                    pdf_r = self.api.get_result_pdf(result["result_id"])
                    if pdf_r.status_code == 200:
                        msg = pdf_r.json().get("message")
                        if msg:
                            console.print(Panel.fit(f"[cyan]{msg}[/cyan]"))
                    else:
                        console.print("[yellow]Result PDF not available yet.[/yellow]")

            else:
                console.print("[red]Failed to finalize quiz or no result available.[/red]")


                console.print(Panel.fit("[cyan]Thank you for taking the quiz! Results also emailed to you.[/cyan]"))
                return
            # save progress
            self.session['last_q'] = current_q
            save_session(self.session)

    # Wallet & Withdraw flows
    def wallet_flow(self):
        console.print(Panel.fit("[bold cyan]💰 Wallet Overview[/bold cyan]"))
        r = self.api.get_wallet()
        if r.status_code != 200:
            console.print(Panel.fit("[red]Failed to fetch wallet[/red]"))
            return
        try:
            w = r.json()
        except Exception:
            console.print(Panel.fit("[red]Invalid wallet response[/red]"))
            return
        # expected fields: points_balance, quizcoin_balance, usd_balance, ngn_balance
        table = Table(title="Wallet Balances", header_style="bold magenta")
        table.add_column("Currency"); table.add_column("Amount", justify="right")
        table.add_row("Points", str(w.get("points_balance", 0)))
        table.add_row("QuizCoin", str(w.get("quizcoin_balance", 0)))
        table.add_row("USD", f"${w.get('usd_balance', 0):,.2f}")
        table.add_row("NGN", f"₦{w.get('ngn_balance', 0):,.2f}")
        console.print(table)

    def withdraw_flow(self):
        console.print(Panel.fit("[bold magenta]💸 Request Withdrawal[/bold magenta]"))
        try:
            amount = float(Prompt.ask("Amount in QuizCoin"))
        except Exception:
            console.print("[red]Invalid amount[/red]"); return
        if amount <= 0:
            console.print("[red]Amount must be positive[/red]"); return
        bank_name = Prompt.ask("Bank name")
        account_number = Prompt.ask("Account number")
        ngn_rate = None
        if Confirm.ask("Do you want to provide current NGN rate? (optional)", default=False):
            try:
                ngn_rate = float(Prompt.ask("NGN rate (USD->NGN)"))
            except Exception:
                ngn_rate = None
        if not Confirm.ask(f"Confirm withdraw {amount} QuizCoin to {account_number} at {bank_name}?"):
            console.print("[yellow]Cancelled[/yellow]"); return
        r = self.api.request_withdraw(amount, ngn_rate, bank_name, account_number)
        if r.status_code in (200,201):
            console.print(Panel.fit("[green]Withdrawal request submitted. Admin will process payout.[/green]"))
            try:
                data = r.json()
                console.print(f"Withdrawal ID: {data.get('withdrawal_id')} Status: {data.get('status')}")
            except Exception:
                pass
        else:
            try:
                err = r.json()
            except Exception:
                err = r.text
            console.print(Panel.fit(f"[red]Withdrawal failed: {err}[/red]"))

    # main run
    def run(self):
        self.display_banner()
        while True:
            try:
                menu = Table.grid(expand=True)
                menu.add_column(justify="center")
                menu.add_row("[bold cyan]1[/bold cyan] Login    [bold cyan]2[/bold cyan] Register    [bold cyan]3[/bold cyan] Recover    [bold cyan]4[/bold cyan] Wallet    [bold cyan]5[/bold cyan] Withdraw    [bold cyan]6[/bold cyan] Exit")
                console.print(Panel.fit(menu, title="[bold cyan]CODEHOUSE CLOUD[/bold cyan]"))
                choice = Prompt.ask("Select", choices=["1","2","3","4","5","6"], default="1")
                if choice == "1":
                    self.login_flow()
                elif choice == "2":
                    self.register_flow()
                elif choice == "3":
                    self.password_recovery_flow()
                elif choice == "4":
                    self.wallet_flow()
                elif choice == "5":
                    self.withdraw_flow()
                else:
                    console.print(Panel.fit("[bold]Goodbye![/bold]\n[dim cyan]Powered by Applinet Technology[/dim cyan]"))
                    sys.exit(0)
            except KeyboardInterrupt:
                console.print()
                if Confirm.ask("Exit application?"):
                    console.print(Panel.fit("Goodbye!", style="blue"))
                    sys.exit(0)
                else:
                    continue




class AuthCLI:
    def __init__(self, api):
        self.api = api

    def appeal(self):
        """
        Sends an appeal email if the user is blocked.
        """
        console.print("[yellow]Submitting your appeal...[/yellow]")
        r = self.api.appeal_block()
        if r.status_code == 200:
            console.print(f"[green]{r.json().get('message')}[/green]")
        else:
            data = r.json()
            console.print(f"[red]{data.get('error', 'Appeal failed')}. Try again later.[/red]")

# =========================
# Entrypoint
# =========================
def main():
    api = APIClient(API_BASE_URL, ENDPOINTS)
    # load existing session tokens into API client
    sess = load_session()
    if sess:
        api.token = sess.get("token")
        api.sti = sess.get("sti")
    cli = QuizCLI(api)
    cli.run()

if __name__ == "__main__":
    main()
