"""Word 文档题库导入工具.

支持从 Word 文档中解析判断题,选择题,填空题,问答题.
支持多种格式的题目识别.
"""

from __future__ import annotations

import re
import uuid
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar

if TYPE_CHECKING:
    from pytola_office.quiztrainer.core import DataManager

# 可选的 python-docx 库
try:
    from docx import Document

    HAS_DOCX = True
except ImportError:
    HAS_DOCX = False


@dataclass
class ParsedQuestion:
    """解析后的题目.

    Attributes
    ----------
    raw_text (str): 原始文本
    q_type (str): 题目类型
    question (str): 题目内容
    options (list[str]): 选项列表
    answer (str | bool): 正确答案
    explanation (str): 解析
    category (str): 分类
    difficulty (str): 难度
    line_number (int): 文档行号
    error (str | None): 解析错误信息
    """

    raw_text: str
    q_type: str = ""
    question: str = ""
    options: list[str] = field(default_factory=list)
    answer: str | bool = ""
    explanation: str = ""
    category: str = "默认分类"
    difficulty: str = "medium"
    line_number: int = 0
    error: str | None = None

    def is_valid(self) -> bool:
        """检查题目是否有效."""
        return self.error is None and bool(self.question)

    def to_dict(self) -> dict[str, Any]:
        """转换为字典."""
        return {
            "id": str(uuid.uuid4())[:8],
            "type": self.q_type,
            "category": self.category,
            "difficulty": self.difficulty,
            "question": self.question,
            "options": self.options,
            "answer": self.answer,
            "explanation": self.explanation,
        }


@dataclass
class QuestionParseResult:
    """题目解析结果.

    Attributes
    ----------
    success (list[ParsedQuestion]): 成功解析的题目
    failed (list[ParsedQuestion]): 解析失败的题目
    total (int): 总行数
    """

    success: list[ParsedQuestion] = field(default_factory=list)
    failed: list[ParsedQuestion] = field(default_factory=list)
    total: int = 0

    @property
    def success_count(self) -> int:
        """成功数量."""
        return len(self.success)

    @property
    def failed_count(self) -> int:
        """失败数量."""
        return len(self.failed)

    @property
    def success_rate(self) -> float:
        """成功率."""
        if self.total == 0:
            return 0.0
        return round(self.success_count / self.total * 100, 1)


class WordImporter:
    """Word 文档题库导入器.

    支持多种题目格式:

    判断题:
        1. Python 是一种编译型语言. (答案:对/错)
        2. [判断] Python 是解释型语言.

    选择题:
        1. 以下哪个是 Python 的列表?(A)
           A. {1, 2, 3}
           B. [1, 2, 3]
           C. (1, 2, 3)
           D. <1, 2, 3>
           答案: B
           解析: ...

    填空题:
        1. Python 中输出函数是 ___()
           答案: print

    问答题:
        1. [问答] 请简述...
           答案: ...
    """

    # 题目类型标记
    TYPE_MARKERS: ClassVar[dict[str, str]] = {
        "判断题": "truefalse",
        "选择题": "choice",
        "填空题": "fill",
        "问答题": "essay",
        "[判断]": "truefalse",
        "[选择]": "choice",
        "[填空]": "fill",
        "[问答]": "essay",
        "[问答题]": "essay",
        "[简答]": "essay",
    }

    # 答案标记
    ANSWER_PATTERNS: ClassVar[list[str]] = [
        r"答案 [::]\s*([A-Za-z\d]+|对 | 错 | 正确 | 错误|true|false)",
        r"答 [::]\s*([A-Za-z\d]+|对 | 错 | 正确 | 错误)",
        r"\(([A-D])\)\s*$",  # (A) 结尾
        r"^\s*([A-D])\.\s+",  # A. 开头
    ]

    # 判断题答案映射
    TRUE_ANSWERS: ClassVar[set[str]] = {"对", "正确", "true", "是", "yes", "t", "y"}
    FALSE_ANSWERS: ClassVar[set[str]] = {"错", "错误", "false", "否", "no", "f", "n"}

    def __init__(self, file_path: str | Path) -> None:
        """初始化导入器.

        Parameters
        ----------
        file_path : str | Path
            Word 文档路径
        """
        self.file_path = Path(file_path)
        self._check_dependency()

    def _check_dependency(self) -> None:
        """检查依赖."""
        if not HAS_DOCX:
            msg = "需要安装 python-docx 库来解析 Word 文档. \n请运行: pip install python-docx"
            raise ImportError(msg)

    def _extract_paragraphs(self) -> list[tuple[int, str]]:
        """提取文档段落.

        Returns
        -------
        list[tuple[int, str]]
            (行号，段落文本) 列表
        """
        doc = Document(str(self.file_path))
        paragraphs = []

        for i, para in enumerate(doc.paragraphs, 1):
            text = para.text.strip()
            if text:
                paragraphs.append((i, text))

        return paragraphs

    def _extract_tables(self) -> list[list[list[str]]]:
        """提取文档表格.

        Returns
        -------
        list[list[list[str]]]
            表格数据列表
        """
        doc = Document(str(self.file_path))
        tables = []

        for table in doc.tables:
            table_data = []
            for row in table.rows:
                row_data = [cell.text.strip() for cell in row.cells]
                table_data.append(row_data)
            tables.append(table_data)

        return tables

    def _detect_question_type(self, text: str) -> str:
        """检测题目类型.

        Parameters
        ----------
        text : str
            题目文本

        Returns
        -------
        str
            题目类型
        """
        # 检查类型标记
        for marker, q_type in self.TYPE_MARKERS.items():
            if marker in text:
                return q_type

        # 检查选择题特征
        if re.search(r"^[A-D]\.\s+", text, re.MULTILINE):
            return "choice"

        # 检查填空题特征
        if "___" in text or "____" in text:
            return "fill"

        # 检查判断题特征
        if re.search(r"[((] 答案 [::]\s*[对错正确错误]", text) and "选择题" not in text:
            return "truefalse"

        return "choice"  # 默认选择题

    def _extract_answer(self, text: str, q_type: str) -> tuple[str | bool, str]:
        """提取答案.

        Parameters
        ----------
        text : str
            题目文本
        q_type : str
            题目类型

        Returns
        -------
        tuple[str | bool, str]
            (答案, 剩余文本)
        """
        answer = ""
        remaining = text

        for pattern in self.ANSWER_PATTERNS:
            match = re.search(pattern, text)
            if match:
                answer = match.group(1).strip()
                remaining = text[: match.start()] + text[match.end() :]
                break

        # 处理判断题答案
        if q_type == "truefalse":
            if answer.upper() in self.TRUE_ANSWERS:
                return True, remaining
            if answer.upper() in self.FALSE_ANSWERS:
                return False, remaining
            if answer.upper() == "A":
                return True, remaining
            if answer.upper() == "B":
                return False, remaining

        return answer.upper() if answer and q_type == "choice" else answer, remaining

    def _extract_options(self, lines: list[str], start_idx: int) -> tuple[list[str], int]:
        """提取选择题选项.

        Parameters
        ----------
        lines : list[str]
            文本行列表
        start_idx : int
            开始索引

        Returns
        -------
        tuple[list[str], int]
            (选项列表, 结束索引)
        """
        options = []
        i = start_idx

        while i < len(lines):
            line = lines[i].strip()

            # 检查是否是选项
            match = re.match(r"^[A-D]\.\s*(.+)", line)
            if match:
                options.append(match.group(1).strip())
                i += 1
            elif re.match(r"^[a-d]\.\s*(.+)", line):
                # 小写字母选项
                options.append(line[2:].strip())
                i += 1
            elif re.match(r"^\d+[.,]\s*(.+)", line):
                # 数字选项
                options.append(re.sub(r"^\d+[.,]\s*", "", line))
                i += 1
            else:
                # 非选项行,停止提取
                break

        return options, i

    def _parse_single_question(self, text: str, line_number: int) -> ParsedQuestion:
        """解析单个题目.

        Parameters
        ----------
        text : str
            题目文本
        line_number : int
            行号

        Returns
        -------
        ParsedQuestion
            解析后的题目
        """
        result = ParsedQuestion(raw_text=text, line_number=line_number)

        try:
            # 检测题目类型
            result.q_type = self._detect_question_type(text)

            # 处理判断题
            if result.q_type == "truefalse":
                # 先移除序号
                clean_text = re.sub(r"^\d+[.,)\s]+", "", text)
                clean_text = re.sub(r"^[一二三四五六七八九十]+[.,)\s]+", "", clean_text)

                # 提取答案
                answer_match = re.search(r"[((] 答案 [::]\s*([对错正确错误]|true|false|[TFtf])[))]?", clean_text)
                if answer_match:
                    answer_text = answer_match.group(1).lower()
                    if answer_text in {"对", "正确", "true", "是", "t", "y"}:
                        result.answer = True
                    else:
                        result.answer = False
                    # 移除答案部分
                    clean_text = clean_text[: answer_match.start()] + clean_text[answer_match.end() :]

                # 清理题目
                result.question = clean_text.strip()

            # 处理选择题
            elif result.q_type == "choice":
                lines = text.split("\n")
                # 第一行是题目
                question_line = lines[0].strip() if lines else ""

                # 移除序号
                question_line = re.sub(r"^\d+[.,)\s]+", "", question_line)
                question_line = re.sub(r"^[一二三四五六七八九十]+[.,)\s]+", "", question_line)

                # 提取答案
                answer_match = re.search(r"答案 [::]\s*([A-D])", text)
                if answer_match:
                    result.answer = answer_match.group(1)

                # 提取选项
                options = []
                for line in lines[1:]:
                    line = line.strip()
                    match = re.match(r"^([A-D])\.\s*(.+)", line)
                    if match:
                        options.append(match.group(2).strip())

                # 如果题目行包含选项格式
                if not options and len(lines) > 1:
                    for line in lines:
                        if re.match(r"^[A-D]\.\s+", line):
                            parts = re.split(r"\.?\s+", line, maxsplit=1)
                            if len(parts) > 1:
                                options.append(parts[1].strip())

                result.question = question_line
                result.options = options

            # 处理填空题
            elif result.q_type == "fill":
                clean_text = re.sub(r"^\d+[.,)\s]+", "", text)
                clean_text = re.sub(r"^[一二三四五六七八九十]+[.,)\s]+", "", clean_text)

                # 提取答案
                answer_match = re.search(r"答案 [::]\s*(.+)", clean_text)
                if answer_match:
                    result.answer = answer_match.group(1).strip()
                    clean_text = clean_text[: answer_match.start()] + clean_text[answer_match.end() :]

                result.question = clean_text.strip()

            # 处理问答题
            else:
                clean_text = re.sub(r"^\d+[.,)\s]+", "", text)
                clean_text = re.sub(r"^[一二三四五六七八九十]+[.,)\s]+", "", clean_text)

                # 提取答案
                answer_match = re.search(r"答案 [::]\s*(.+)", clean_text)
                if answer_match:
                    result.answer = answer_match.group(1).strip()
                    clean_text = clean_text[: answer_match.start()] + clean_text[answer_match.end() :]

                result.question = clean_text.strip()

            # 提取解析
            explain_match = re.search(r"解析 [::]\s*(.+?)(?=答案|$)", text, re.DOTALL)
            if explain_match:
                result.explanation = explain_match.group(1).strip()

            # 验证题目
            if not result.question:
                result.error = "题目内容为空"
            elif result.q_type == "choice" and not result.options:
                result.error = "选择题缺少选项"

        except (OSError, ValueError, TypeError) as e:
            result.error = f"解析错误:{e!s}"

        return result

    def _parse_from_text(self, text: str, line_offset: int = 0) -> list[ParsedQuestion]:
        """从文本解析题目.

        Parameters
        ----------
        text : str
            文本内容
        line_offset : int
            行号偏移

        Returns
        -------
        list[ParsedQuestion]
            解析后的题目列表
        """
        questions = []
        lines = text.strip().split("\n")

        current_text = ""
        current_line = 0

        for i, line in enumerate(lines):
            line_num = i + 1 + line_offset
            stripped = line.strip()

            if not stripped:
                continue

            # 检查是否是新题目的开始
            is_new_question = False

            # 1. 数字序号开头
            if (
                re.match(r"^\d+[.,)\s]", stripped)
                or re.match(r"^[一二三四五六七八九十]+[.,)\s]", stripped)
                or any(stripped.startswith(m) for m in self.TYPE_MARKERS)
                or re.search(r"[((] 答案 [::]\s*[对错正确错误]", stripped)
            ):
                is_new_question = True
            # 5. 选择题选项开头(连续多行)
            elif re.match(r"^[A-D]\.\s+", stripped):
                # 检查是否紧跟题目
                if current_text and not any(stripped.startswith(m) for m in self.TYPE_MARKERS):
                    # 认为是前一题的选项
                    current_text += "\n" + stripped
                    continue
                is_new_question = True

            if is_new_question:
                # 保存前一个题目
                if current_text:
                    q = self._parse_single_question(current_text, current_line)
                    if q.question:
                        questions.append(q)

                current_text = stripped
                current_line = line_num
            else:
                current_text += "\n" + stripped

        # 保存最后一个题目
        if current_text:
            q = self._parse_single_question(current_text, current_line)
            if q.question:
                questions.append(q)

        return questions

    def parse(self) -> QuestionParseResult:
        """解析 Word 文档.

        Returns
        -------
        QuestionParseResult
            解析结果
        """
        result = QuestionParseResult()

        # 检查文件是否存在
        if not self.file_path.exists():
            return QuestionParseResult()

        # 检查文件类型
        if self.file_path.suffix.lower() not in {".docx", ".doc"}:
            return QuestionParseResult()

        try:
            # 提取段落
            paragraphs = self._extract_paragraphs()
            result.total = len(paragraphs)

            # 合并段落文本
            full_text = "\n".join(text for _, text in paragraphs)

            # 解析题目
            questions = self._parse_from_text(full_text)

            # 分类成功和失败的题目
            for q in questions:
                if q.is_valid():
                    result.success.append(q)
                else:
                    result.failed.append(q)

        except (OSError, ValueError, TypeError) as e:
            # 解析整体失败
            failed_q = ParsedQuestion(raw_text="", line_number=0, error=f"文件解析失败:{e!s}")
            result.failed.append(failed_q)

        return result

    def parse_preview(self, max_questions: int = 10) -> QuestionParseResult:
        """预览解析结果(限制数量).

        Parameters
        ----------
        max_questions : int
            最大预览题目数

        Returns
        -------
        QuestionParseResult
            预览结果
        """
        result = self.parse()

        # 限制预览数量
        result.success = result.success[:max_questions]

        return result

    def save_to_bank(
        self,
        result: QuestionParseResult,
        bank_name: str,
        data_manager: DataManager | None = None,
    ) -> dict[str, int]:
        """保存解析结果到题库.

        Parameters
        ----------
        result : QuestionParseResult
            解析结果
        bank_name : str
            题库名称
        data_manager : Any
            数据管理器

        Returns
        -------
        dict[str, int]
            保存结果统计
        """
        from pytola_office.quiztrainer.core import DataManager
        from pytola_office.quiztrainer.models import BaseQuestion

        dm = data_manager or DataManager()
        saved_count = 0

        for q in result.success:
            question = BaseQuestion.from_dict(q.to_dict())
            dm.add_question(question, bank_name)
            saved_count += 1

        return {"saved": saved_count, "failed": result.failed_count}


def import_from_file(
    file_path: str | Path,
    bank_name: str | None = None,
    *,
    preview: bool = False,
    max_preview: int = 10,
) -> QuestionParseResult:
    """从文件导入题目.

    Parameters
    ----------
    file_path : str | Path
        文件路径
    bank_name : str | None
        题库名称
    preview : bool
        是否仅预览
    max_preview : int
        最大预览数量

    Returns
    -------
    QuestionParseResult
        解析结果
    """
    importer = WordImporter(file_path)

    result = importer.parse_preview(max_preview) if preview else importer.parse()

    # 保存到题库
    if not preview and bank_name and result.success:
        importer.save_to_bank(result, bank_name)

    return result
