from __future__ import annotations

import platform
import select
import random
import string
import sys
import threading
import time
from datetime import datetime
from pathlib import Path
from typing import Any, Literal

import questionary
from rich.console import Console
from rich.table import Table
from rich import box
from rich.panel import Panel
from rich.prompt import Prompt

from qm2.utils.files import load_json, save_json

console = Console()

QuizResult = Literal["correct", "wrong", "timeout", "quit"]


def _is_valid_question(q: Any) -> bool:
    """Return True if q has the minimal structure to be used in a quiz."""
    import json
    
    if not isinstance(q, dict):
        return False
    if "type" not in q or "question" not in q:
        return False
    if q["type"] == "match":
        pairs = q.get("pairs", {})
        # Handle pairs as string (from CSV) or dict
        if isinstance(pairs, str):
            try:
                pairs = json.loads(pairs)
            except (json.JSONDecodeError, TypeError):
                return False
        
        return bool(
            pairs.get("left") and pairs.get("right") and pairs.get("answers")
        )
    return "correct" in q


def input_with_timeout(prompt: str, timeout: int = 60) -> str | None:
    if platform.system() == "Windows":
        result = {"value": None}

        def _get_input():
            try:
                result["value"] = input(f"{prompt} ").strip()
            except (EOFError, KeyboardInterrupt):
                result["value"] = None

        t = threading.Thread(target=_get_input, daemon=True)
        t.start()
        t.join(timeout)
        if t.is_alive():
            return None
        return result["value"]

    try:
        sys.stdout.write(f"{prompt} ")
        sys.stdout.flush()
        rlist, _, _ = select.select([sys.stdin], [], [], timeout)
        if rlist:
            line = sys.stdin.readline()
            return line.rstrip("\n")
        return None
    except Exception:
        return None


def _check_quit_confirmation(message: str = "Do you want to stop the quiz?") -> bool:
    """Return True if user confirms quit."""
    return bool(questionary.confirm(message).ask())


def _handle_choice_question(q: dict[str, Any]) -> QuizResult:
    """Handle multiple choice or true/false question. Returns result type."""
    # Ensure wrong_answers is always a list
    wrong_answers = q["wrong_answers"]
    if isinstance(wrong_answers, str):
        wrong_answers = [wrong_answers]
    
    options = [q["correct"]] + wrong_answers
    random.shuffle(options)
    option_labels = list(string.ascii_lowercase)[: len(options)]

    table = Table(box=box.SIMPLE)
    table.add_column("Option", style="cyan")
    table.add_column("Answer")
    for label, option in zip(option_labels, options, strict=False):
        # Convert option to string to handle boolean values
        option_str = str(option) if not isinstance(option, str) else option
        table.add_row(label, option_str)
    console.print(table)

    remaining_time = 60
    start_time_q = time.time()

    while True:
        elapsed = time.time() - start_time_q
        remaining = int(remaining_time - elapsed)

        if remaining <= 0:
            console.print(f"[red]❌ Time is up. Correct answer: [bold]{q['correct']}[/]")
            return "timeout"

        answer = input_with_timeout(
            f"⏳ {remaining}s - Your choice (letter, x to quit):", remaining
        )

        if answer is None:
            console.print(f"[red]❌ Time is up. Correct answer: [bold]{q['correct']}[/]")
            return "timeout"

        answer = answer.strip().lower()

        if answer == "x":
            if _check_quit_confirmation():
                console.print("[yellow]⏹️ Quiz stopped by the user.")
                return "quit"
            continue

        if answer in option_labels:
            selected = options[option_labels.index(answer)]
            if str(selected).lower().strip() == str(q["correct"]).lower().strip():
                console.print("[green]✅ Correct!")
                return "correct"
            console.print(f"[red]❌ Wrong. The correct answer is: [bold]{q['correct']}[/]")
            return "wrong"

        console.print("[yellow]⚠️ Invalid input.")


def _handle_fillin_question(q: dict[str, Any]) -> QuizResult:
    """Handle fill-in question. Returns result type."""
    remaining_time = 60
    start_time_q = time.time()

    while True:
        elapsed = time.time() - start_time_q
        remaining = int(remaining_time - elapsed)

        if remaining <= 0:
            console.print(f"[red]❌ Time is up. Correct answer: [bold]{q['correct']}[/]")
            return "timeout"

        answer = input_with_timeout(
            f"⏳ {remaining}s - Enter the correct answer (or x to quit):", remaining
        )

        if answer is None:
            console.print(f"[red]❌ Time is up. Correct answer: [bold]{q['correct']}[/]")
            return "timeout"

        answer = answer.strip()

        if answer.lower() == "x":
            if _check_quit_confirmation():
                console.print("[yellow]⏹️ Quiz stopped by the user.")
                return "quit"
            continue

        if answer.lower() == str(q["correct"]).lower():
            console.print("[green]✅ Correct!")
            return "correct"
        console.print(f"[red]❌ Wrong. The correct answer is: [bold]{q['correct']}[/]")
        return "wrong"


def _handle_match_question(q: dict[str, Any]) -> QuizResult:
    """Handle matching question. Returns result type."""
    import json
    
    pairs = q.get("pairs", {})
    # Handle pairs as string (from CSV) or dict
    if isinstance(pairs, str):
        try:
            pairs = json.loads(pairs)
        except (json.JSONDecodeError, TypeError):
            console.print("[red]⚠️ Matching question has invalid pairs format.")
            return "wrong"
    
    left = pairs.get("left", [])
    right = pairs.get("right", [])
    correct_mapping = pairs.get("answers", {})

    if not left or not right or not correct_mapping:
        console.print("[red]⚠️ Matching question is not properly defined.")
        return "wrong"

    console.print("[cyan]Match the items (input: a-1, b-2, ...):")
    table = Table.grid(padding=(0, 3))
    for i, (left_val, right_val) in enumerate(zip(left, right, strict=False), start=1):
        table.add_row(
            f"[bold]{chr(96 + i)})[/bold] {left_val}", f"[bold]{i}.[/bold] {right_val}"
        )
    console.print(table)

    user_mapping: dict[str, str] = {}
    remaining_time = 60
    start_time_q = time.time()

    for i in range(len(left)):
        elapsed = time.time() - start_time_q
        remaining = int(remaining_time - elapsed)

        if remaining <= 0:
            console.print("[red]❌ Time is up. You didn't complete the question.")
            return "timeout"

        l_item = left[i]
        input_val = input_with_timeout(
            f"⏳ {remaining}s - Enter the number for pair {chr(97 + i)}) {l_item} (or x to quit):",
            remaining,
        )

        if input_val is None:
            console.print(f"[red]❌ Time is up. You didn't answer {chr(97 + i)})")
            return "timeout"

        if input_val.lower() == "x":
            if _check_quit_confirmation():
                console.print("[yellow]⏹️ Quiz stopped by the user.")
                return "quit"
            continue

        user_mapping[chr(97 + i)] = input_val

    if len(user_mapping) != len(correct_mapping):
        return "timeout"

    for k, v in correct_mapping.items():
        if user_mapping.get(k) != str(v):
            console.print("[red]❌ Incorrect. Correct mapping:")
            console.print(f"[bold yellow]{correct_mapping}[/]")
            return "wrong"

    console.print("[green]✅ You matched all pairs correctly!")
    return "correct"


def _show_quiz_statistics(
    correct_count: int,
    wrong_count: int,
    timeout_count: int,
    total: int,
    duration: int,
) -> None:
    """Display quiz statistics."""
    console.rule("[bold magenta]📊 Quiz statistics")
    console.print(f"[green]✅ Correct answers: {correct_count}")
    console.print(f"[red]❌ Wrong answers: {wrong_count}")
    console.print(f"[yellow]⏱️ Unanswered: {timeout_count}")
    console.print(f"[blue]🕓 Total time: {duration} seconds")
    console.print(f"[bold]🎯 Score: {correct_count}/{total} correct")


def _save_quiz_result(
    score_file: str | Path,
    quiz_name: str,
    correct_count: int,
    wrong_count: int,
    timeout_count: int,
    total: int,
    duration: int,
) -> None:
    """Save quiz result to file."""
    scores = load_json(str(score_file))
    scores.append(
        {
            "quiz_name": quiz_name,
            "correct": correct_count,
            "wrong": wrong_count,
            "unanswered": timeout_count,
            "total": total,
            "duration_s": duration,
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        }
    )
    if not save_json(str(score_file), scores):
        console.print("[red]⚠️ Failed to save quiz results.")


def quiz_session(questions: list[dict[str, Any]], score_file: str | Path, quiz_name: str = "Unknown Quiz") -> None:
    if not questions:
        console.print("[red]⚠️ No available questions.")
        return

    valid_questions = [q for q in questions if _is_valid_question(q)]
    skipped = len(questions) - len(valid_questions)
    if skipped > 0:
        console.print(f"[yellow]⚠️ Skipped {skipped} invalid question(s).[/yellow]")
    if not valid_questions:
        console.print("[red]⚠️ No valid questions available.")
        return

    questions = valid_questions
    random.shuffle(questions)
    correct_count = wrong_count = timeout_count = 0
    start_time = time.time()

    total_q = len(questions)
    for i, q in enumerate(questions, start=1):
        
        # Milestone Notification (every 15 questions)
        if i > 1 and (i - 1) % 15 == 0:
            current_accuracy = (correct_count / (i - 1)) * 100
            console.print("\n")

            if current_accuracy >= 80:
                msg = "🚀 [bold green]Amazing work![/bold green] You're crushing it!"
            elif current_accuracy >= 50:
                msg = "⚖️ [bold yellow]Not bad![/bold yellow] Keep a steady pace."
            else:
                msg = "🧗 [bold red]Don't give up![/bold red] Reviewing the flashcards might help."
            
            # English comment: Construct the milestone panel content into a single f-string
            panel_content = (
                f"🎯 [bold]Checkpoint reached![/bold]\n"
                f"📝 Finished: [cyan]{i-1}/{total_q}[/cyan] questions\n"
                f"📈 Accuracy: [bold]{current_accuracy:.1f}%[/bold]\n\n"
                f"{msg}"
            )

            console.print(Panel(
                panel_content,
                title="[yellow]Milestone[/yellow]",
                border_style="bright_magenta",
                expand=False
            ))
            time.sleep(1.5)

        # English comment: Calculate visual progress components
        progress_pct = int(((i - 1) / total_q) * 100)
        bar_len = 20
        filled = int(bar_len * (i - 1) // total_q)
        bar = "█" * filled + "░" * (bar_len - filled)

        # Use the calculated bar and percentage in the header rule
        header_text = f"Question {i}/{total_q} | {bar} | {progress_pct}%"
        console.rule(f"[bold blue]{header_text}", characters="━")
        
        console.print(f"\n[bold]{q['question']}[/bold]")

        if q["type"] in ("multiple", "truefalse"):
            result = _handle_choice_question(q)
        elif q["type"] == "fillin":
            result = _handle_fillin_question(q)
        elif q["type"] == "match":
            result = _handle_match_question(q)
        else:
            continue

        if result == "quit":
            return
        if result == "correct":
            correct_count += 1
        elif result == "wrong":
            wrong_count += 1
        elif result == "timeout":
            timeout_count += 1

    duration = int(time.time() - start_time)
    _show_quiz_statistics(correct_count, wrong_count, timeout_count, len(questions), duration)
    _save_quiz_result(score_file, quiz_name, correct_count, wrong_count, timeout_count, len(questions), duration)

def flashcards_mode(questions: list[dict[str, Any]]) -> None:
    if not questions:
        console.print("[red]⚠️ No questions for flashcards.")
        return

    valid_questions = [q for q in questions if _is_valid_question(q)]
    if not valid_questions:
        console.print("[red]⚠️ No valid questions available.")
        return

    questions = valid_questions
    random.shuffle(questions)
    
    # Iterate through flashcards with progress tracking
    total_q = len(questions)
    for i, q in enumerate(questions, start=1):
        
        # Milestone / Break
        # English comment: Periodic break to prevent study fatigue
        if i > 1 and (i - 1) % 15 == 0:
            console.clear()
            console.print(Panel(
                f"🧠 [bold]Flashcard Milestone[/bold]\n"
                f"📖 You have reviewed [cyan]{i-1}/{total_q}[/cyan] cards.\n"
                f"🌟 Take a deep breath and keep going!",
                title="[yellow]Study Break[/yellow]",
                border_style="bold magenta",
                expand=False
            ))
            Prompt.ask("\nPress Enter when you are ready to continue")
            console.clear()

        # Progress Header
        # Calculate visual progress components
        progress_pct = int(((i - 1) / total_q) * 100)
        bar_len = 20
        filled = int(bar_len * (i - 1) // total_q)
        bar = "█" * filled + "░" * (bar_len - filled)
        
        header_text = f"Flashcard {i}/{total_q} | {bar} | {progress_pct}%"
        console.rule(f"[bold cyan]{header_text}", characters="━")

        # Question Display
        console.print(Panel(
            f"[bold]{q['question']}[/bold]",
            subtitle="[dim]Question[/dim]",
            border_style="bright_blue"
        ))

        # Wait for user to reveal the answer
        ans = Prompt.ask("Press Enter to reveal answer or 'x' to exit", default="")
        if ans.strip().lower() == "x":
            if _check_quit_confirmation("Do you want to exit flashcards mode?"):
                break

        # Answer Display
        if q["type"] == "match":
            import json
            pairs = q.get("pairs", {})
            if isinstance(pairs, str):
                try:
                    pairs = json.loads(pairs)
                except (json.JSONDecodeError, TypeError):
                    console.print("[red]⚠️ Invalid matching format in flashcard.")
                    continue
            
            left, right, correct = pairs.get("left", []), pairs.get("right", []), pairs.get("answers", {})
            table = Table(box=box.SIMPLE)
            table.add_column("Left", style="cyan")
            table.add_column("Right", style="magenta")
            for key, val in correct.items():
                l_item = left[ord(key) - 97]
                r_item = right[int(val) - 1]
                table.add_row(f"{key}) {l_item}", f"{val}. {r_item}")
            
            console.print("[green]✅ Correct matching:")
            console.print(table)
        else:
            console.print(Panel(
                f"[bold green]{q['correct']}[/bold green]",
                title="Answer",
                border_style="green",
                expand=False
            ))

        # Manual transition to next card or exit
        cont = Prompt.ask("\nPress Enter for next card or 'x' to exit", default="")
        if cont.strip().lower() == "x":
            if _check_quit_confirmation("Do you want to exit flashcards mode?"):
                break
        
        console.clear() # Clear screen for the next card to maintain focus