"""Structured export helpers for SavedQuiz admin actions."""

# Standard Library
import re
import unicodedata
from html import unescape

# Django
from django.http import HttpResponse
from django.utils.html import strip_tags
from django.utils.text import slugify
from django.utils.translation import gettext as _

# Third party
from openpyxl import Workbook
from openpyxl.styles import Font

BASE_COLUMNS = [
    "id",
    "uuid",
    "started_quiz",
    "quiz_type",
    "conclusion_points",
    "conclusion_text",
]
MAX_COLUMN_WIDTH = 85.0  # ~= 600px in Excel units
MAX_SHEET_TITLE_LENGTH = 31


def _safe_slug(value):
    slug = slugify(str(value or ""))
    return slug or "unknown"


def _to_text(value):
    if value is None:
        return ""
    cleaned = strip_tags(str(value))
    # Decode HTML entities coming from rich text fields (&eacute;, &nbsp;, ...)
    return unescape(cleaned).replace("\xa0", " ")


def _question_label(answer_entry):
    question_payload = answer_entry.get("question", {})
    if isinstance(question_payload, dict):
        return _to_text(question_payload.get("label", ""))
    return _to_text(question_payload)


def _question_answer(answer_entry):
    selected_answers = answer_entry.get("answers")
    if isinstance(selected_answers, list):
        labels = []
        for item in selected_answers:
            if isinstance(item, dict):
                if item.get("selected"):
                    labels.append(_to_text(item.get("label", "")))
            elif item is not None:
                labels.append(_to_text(item))
        return " | ".join(label for label in labels if label)
    return _to_text(answer_entry.get("answer", ""))


def _question_explanation(answer_entry):
    if "question_explanation_text" in answer_entry:
        return _to_text(answer_entry.get("question_explanation_text", ""))
    question_payload = answer_entry.get("question", {})
    if isinstance(question_payload, dict):
        return _to_text(question_payload.get("question_explanation_text", ""))
    return ""


def _flatten_saved_quiz(saved_quiz):
    row = {
        "id": saved_quiz.id,
        "uuid": str(saved_quiz.uuid),
        "started_quiz": saved_quiz.started_quiz.isoformat()
        if saved_quiz.started_quiz
        else "",
        "quiz_type": _to_text(saved_quiz.quiz_type),
        "conclusion_points": "",
        "conclusion_text": "",
    }

    conclusion = (
        saved_quiz.conclusion if isinstance(saved_quiz.conclusion, dict) else {}
    )
    row["conclusion_points"] = conclusion.get("points", "")
    if "description" in conclusion:
        row["conclusion_text"] = _to_text(conclusion["description"])
    elif saved_quiz.conclusion:
        row["conclusion_text"] = _to_text(saved_quiz.conclusion)

    for info in saved_quiz.more_infos or []:
        info_key = f"info__{_safe_slug(info.get('question', 'question'))}"
        row[info_key] = _to_text(info.get("answer", ""))

    questions = [
        {
            "index": index,
            "label": _question_label(answer_entry),
            "answer": _question_answer(answer_entry),
            "question_explanation": _question_explanation(answer_entry),
        }
        for index, answer_entry in enumerate(saved_quiz.answers or [], start=1)
    ]
    return row, questions


def _base_label(column_key):
    labels = {
        "id": _("ID"),
        "uuid": _("UUID"),
        "started_quiz": _("Start date"),
        "quiz_type": _("Quiz name"),
        "conclusion_points": _("Points earned"),
        "conclusion_text": _("Conclusion obtained"),
    }
    return labels.get(column_key, column_key)


def _info_header_label(info_key, info_label_by_key):
    """Use human-readable question text; slug is only an internal column key."""
    return info_label_by_key.get(info_key) or info_key.replace("info__", "")


def _question_column_label(index, question_text):
    return _("Question #%(index)s: %(question)s") % {
        "index": index,
        "question": question_text or _("No label"),
    }


def _answer_column_label(index):
    return _("Answer question #%(index)s") % {"index": index}


def _question_explanation_column_label(index):
    return _("Explanatory text question #%(index)s") % {"index": index}


def _quiz_group_key(saved_quiz):
    if saved_quiz.weighted_answers_quiz_id:
        quiz_obj = saved_quiz.weighted_answers_quiz
        return (
            f"wa_{saved_quiz.weighted_answers_quiz_id}",
            saved_quiz.weighted_answers_quiz_id,
            quiz_obj.name if quiz_obj else str(saved_quiz.quiz_type),
            getattr(quiz_obj, "create_date", None),
            bool(getattr(quiz_obj, "show_answers_corrections_text", False)),
        )
    if saved_quiz.interpretation_quiz_id:
        quiz_obj = saved_quiz.interpretation_quiz
        return (
            f"int_{saved_quiz.interpretation_quiz_id}",
            saved_quiz.interpretation_quiz_id,
            quiz_obj.name if quiz_obj else str(saved_quiz.quiz_type),
            getattr(quiz_obj, "create_date", None),
            False,
        )
    return (
        f"unknown_{saved_quiz.id}",
        saved_quiz.id,
        str(saved_quiz.quiz_type),
        None,
        False,
    )


def _group_saved_quizzes(saved_quizzes):
    groups = {}
    for saved_quiz in saved_quizzes:
        key, quiz_id, display_name, created_at, show_explanations = _quiz_group_key(
            saved_quiz
        )
        groups.setdefault(
            key,
            {
                "quiz_id": quiz_id,
                "display_name": display_name,
                "created_at": created_at,
                "show_explanations": show_explanations,
                "items": [],
            },
        )
        groups[key]["items"].append(saved_quiz)

    def _sort_key(item):
        created_at = item[1]["created_at"]
        return created_at or ""

    return sorted(groups.items(), key=_sort_key, reverse=True)


def _build_group_table(saved_quizzes, include_question_explanations=False):
    normalized_rows = []
    info_headers = []
    info_seen = set()
    info_label_by_key = {}
    question_labels_by_index = {}
    max_question_index = 0

    for saved_quiz in saved_quizzes:
        row, questions = _flatten_saved_quiz(saved_quiz)
        normalized_rows.append((row, questions))

        for info in saved_quiz.more_infos or []:
            info_key = f"info__{_safe_slug(info.get('question', 'question'))}"
            label = _to_text(info.get("question", ""))
            if info_key not in info_label_by_key and label:
                info_label_by_key[info_key] = label

        for key in row.keys():
            if key.startswith("info__") and key not in info_seen:
                info_seen.add(key)
                info_headers.append(key)

        for question in questions:
            max_question_index = max(max_question_index, question["index"])
            if question["index"] not in question_labels_by_index and question["label"]:
                question_labels_by_index[question["index"]] = question["label"]

    headers = []
    headers.extend([(key, _base_label(key)) for key in BASE_COLUMNS])
    headers.extend(
        [(key, _info_header_label(key, info_label_by_key)) for key in info_headers]
    )
    for index in range(1, max_question_index + 1):
        question_text = question_labels_by_index.get(index, "")
        headers.append(
            (f"q{index:02d}__question", _question_column_label(index, question_text))
        )
        headers.append((f"q{index:02d}__answer", _answer_column_label(index)))
        if include_question_explanations:
            headers.append(
                (
                    f"q{index:02d}__question_explanation",
                    _question_explanation_column_label(index),
                )
            )

    rows = []
    for row_data, questions in normalized_rows:
        flat_row = {key: row_data.get(key, "") for key, _ in headers}
        for question in questions:
            flat_row[f"q{question['index']:02d}__question"] = question["label"]
            flat_row[f"q{question['index']:02d}__answer"] = question["answer"]
            if include_question_explanations:
                flat_row[f"q{question['index']:02d}__question_explanation"] = question[
                    "question_explanation"
                ]
        rows.append(flat_row)

    return headers, rows


def _sanitize_sheet_title_text(value):
    normalized = unicodedata.normalize("NFKD", str(value or ""))
    ascii_only = normalized.encode("ascii", "ignore").decode("ascii")
    cleaned = re.sub(r"[^A-Za-z0-9 _-]+", "", ascii_only)
    cleaned = re.sub(r"\s+", " ", cleaned).strip()
    return cleaned or "quiz"


def _sheet_title(quiz_id, display_name):
    prefix = f"#{quiz_id} - "
    safe_name = _sanitize_sheet_title_text(display_name)
    max_name_length = max(1, MAX_SHEET_TITLE_LENGTH - len(prefix))
    return f"{prefix}{safe_name[:max_name_length]}".strip()


def _apply_header_style_and_width(worksheet):
    for cell in worksheet[1]:
        cell.font = Font(name="Arial", bold=True)

    for column in worksheet.columns:
        letter = column[0].column_letter
        max_length = 0
        for cell in column:
            max_length = max(max_length, len(str(cell.value or "")))
        worksheet.column_dimensions[letter].width = min(
            MAX_COLUMN_WIDTH, max_length + 2
        )


def xlsx_response(saved_quizzes, filename):
    workbook = Workbook()
    default_sheet = workbook.active
    workbook.remove(default_sheet)

    ended_quizzes = [
        saved_quiz for saved_quiz in saved_quizzes if saved_quiz.quiz_ended
    ]
    grouped = _group_saved_quizzes(ended_quizzes)

    for group_key, group_data in grouped:
        group_items = [item for item in group_data["items"] if item.answers]
        if not group_items:
            continue

        headers, rows = _build_group_table(
            group_items,
            include_question_explanations=group_data["show_explanations"],
        )
        sheet_title = _sheet_title(group_data["quiz_id"], group_data["display_name"])
        worksheet = workbook.create_sheet(title=sheet_title)
        worksheet.append([label for _, label in headers])
        for row in rows:
            worksheet.append([row.get(key, "") for key, _ in headers])
        _apply_header_style_and_width(worksheet)

    if not workbook.sheetnames:
        worksheet = workbook.create_sheet(title="saved_quizzes")
        fallback_headers = [_base_label(key) for key in BASE_COLUMNS]
        worksheet.append(fallback_headers)
        _apply_header_style_and_width(worksheet)

    response = HttpResponse(
        content_type=(
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        )
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    workbook.save(response)
    return response
