#!/usr/bin/env python3
from __future__ import annotations

import csv
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable

import xlrd


ROOT = Path(__file__).resolve().parents[2]
OUT_DIR = ROOT / "backend" / "src" / "main" / "resources" / "question-bank"

EPQ_XLS = ROOT / "题_2058艾森克人格问卷(EPQ）题目.xls"
SCL_XLS = ROOT / "题_2078SCL-90测验90题.xls"
SAS_DOCX = ROOT / "题_SAS焦虑自评量表（梁修改）.docx"
SDS_DOC = ROOT / "题_SDS抑郁自评量表（梁修改）.doc"
SAQ_DOCX = ROOT / "题_SAQ自杀态度调查问卷（梁修改）.docx"


@dataclass
class QuestionRow:
  question_code: str
  prompt: str
  options: str
  reverse: int
  dimension: str
  sort_order: int


def text_from_doc(path: Path) -> str:
  output = subprocess.check_output(["textutil", "-convert", "txt", "-stdout", str(path)])
  return output.decode("utf-8", errors="ignore")


def write_csv(name: str, rows: Iterable[QuestionRow]) -> None:
  OUT_DIR.mkdir(parents=True, exist_ok=True)
  target = OUT_DIR / name
  with target.open("w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["question_code", "prompt", "options", "reverse", "dimension", "sort_order"])
    for row in rows:
      writer.writerow([
          row.question_code,
          row.prompt,
          row.options,
          row.reverse,
          row.dimension,
          row.sort_order,
      ])


def parse_legacy_sheet(path: Path, sheet: str, keep: callable) -> list[dict]:
  wb = xlrd.open_workbook(str(path))
  sh = wb.sheet_by_name(sheet)
  records: list[dict] = []
  cur: dict | None = None
  for r in range(sh.nrows):
    c0 = str(sh.cell_value(r, 0)).strip()
    c1 = str(sh.cell_value(r, 1)).strip()
    c2 = str(sh.cell_value(r, 2)).strip()
    if c1 == "试题ID":
      if cur and keep(cur):
        records.append(cur)
      try:
        qid = int(float(c2))
      except ValueError:
        qid = int(c2)
      cur = {"id": qid, "prompt": "", "options": []}
      continue
    if not cur:
      continue
    if c1 == "试题内容":
      cur["prompt"] = c2
    elif c0 == "○" and re.fullmatch(r"\d+", c1):
      cur["options"].append((int(c1), c2))
  if cur and keep(cur):
    records.append(cur)
  return records


def build_epq() -> None:
  records = parse_legacy_sheet(EPQ_XLS, "2058", lambda q: 2058001 <= q["id"] <= 2058088)
  rows: list[QuestionRow] = []
  for item in records:
    idx = item["id"] - 2058000
    options = "|".join(text for _, text in sorted(item["options"], key=lambda x: x[0]))
    rows.append(
        QuestionRow(
            question_code=f"2058{idx:03d}",
            prompt=item["prompt"],
            options=options,
            reverse=0,
            dimension="EPQ人格维度",
            sort_order=idx,
        )
    )
  if len(rows) != 88:
    raise RuntimeError(f"EPQ 题量异常，期望 88，实际 {len(rows)}")
  write_csv("EPQ-2058.csv", rows)


def build_scl90() -> None:
  records = parse_legacy_sheet(SCL_XLS, "2078", lambda q: True)
  rows: list[QuestionRow] = []
  for item in records:
    qid = item["id"]
    # 原始文件存在 2077056，按量表序号修正为 2078056。
    if qid == 2077056:
      qid = 2078056
    idx = qid % 1000
    if not (1 <= idx <= 90):
      continue
    options = "|".join(text for _, text in sorted(item["options"], key=lambda x: x[0]))
    rows.append(
        QuestionRow(
            question_code=f"2078{idx:03d}",
            prompt=item["prompt"],
            options=options,
            reverse=0,
            dimension="SCL90症状维度",
            sort_order=idx,
        )
    )
  rows.sort(key=lambda x: x.sort_order)
  if len(rows) != 90:
    raise RuntimeError(f"SCL-90 题量异常，期望 90，实际 {len(rows)}")
  if [r.sort_order for r in rows] != list(range(1, 91)):
    raise RuntimeError("SCL-90 题号不连续")
  write_csv("SCL90-2078.csv", rows)


def build_sas() -> None:
  text = text_from_doc(SAS_DOCX)
  lines = [line.strip() for line in text.splitlines() if line.strip()]
  rows: list[QuestionRow] = []
  reverse = {5, 9, 13, 17, 19}
  p = re.compile(r"^\*?\s*(\d{1,2})\.\s*(.+)$")
  for line in lines:
    m = p.match(line)
    if not m:
      continue
    idx = int(m.group(1))
    if not (1 <= idx <= 20):
      continue
    prompt = m.group(2).strip()
    rows.append(
        QuestionRow(
            question_code=f"2083{idx:03d}",
            prompt=prompt,
            options="没有或很少时间有|小部分时间有|相当多时间有|绝大部分时间有",
            reverse=1 if idx in reverse else 0,
            dimension="SAS焦虑",
            sort_order=idx,
        )
    )
  rows = dedup_rows(rows, 20, "SAS")
  write_csv("SAS-2083.csv", rows)


def build_sds() -> None:
  text = text_from_doc(SDS_DOC)
  lines = [line.strip() for line in text.splitlines() if line.strip()]
  rows: list[QuestionRow] = []
  reverse = {2, 5, 6, 11, 12, 14, 16, 17, 18, 20}
  option = "A没有或很少时间|B小部分时间|C相当多时间|D绝大部分或全部时间"
  p = re.compile(r"^(.+?)\s+A\s+B\s+C\s+D$")
  idx = 1
  for line in lines:
    m = p.match(line)
    if not m:
      continue
    if idx > 20:
      break
    prompt = m.group(1).strip("：: ")
    rows.append(
        QuestionRow(
            question_code=f"2082{idx:03d}",
            prompt=prompt,
            options=option,
            reverse=1 if idx in reverse else 0,
            dimension="SDS抑郁",
            sort_order=idx,
        )
    )
    idx += 1
  rows = dedup_rows(rows, 20, "SDS")
  write_csv("SDS-2082.csv", rows)


def build_sqa() -> None:
  text = text_from_doc(SAQ_DOCX)
  lines = [line.strip() for line in text.splitlines() if line.strip()]
  rows: list[QuestionRow] = []
  p = re.compile(r"^(\d{1,2})\.\s*(.+?)\s*12345$")
  for line in lines:
    m = p.match(line)
    if not m:
      continue
    idx = int(m.group(1))
    if not (1 <= idx <= 29):
      continue
    rows.append(
        QuestionRow(
            question_code=f"2084{idx:03d}",
            prompt=m.group(2).strip(),
            options="完全赞同|赞同|中立|不赞同|完全不赞同",
            reverse=0,
            dimension="SQA自杀态度",
            sort_order=idx,
        )
    )
  rows = dedup_rows(rows, 29, "SQA")
  write_csv("SQA-2084.csv", rows)


def dedup_rows(rows: list[QuestionRow], expected: int, name: str) -> list[QuestionRow]:
  table = {}
  for row in rows:
    table[row.sort_order] = row
  out = [table[i] for i in sorted(table.keys())]
  if len(out) != expected or [x.sort_order for x in out] != list(range(1, expected + 1)):
    raise RuntimeError(f"{name} 题量/题号异常: {len(out)}")
  return out


def write_report() -> None:
  report = OUT_DIR / "README.md"
  report.write_text(
      "\n".join([
          "# 题库结构化产物",
          "",
          "- `EPQ-2058.csv`: 88 题（不含基础信息题）",
          "- `SCL90-2078.csv`: 90 题（已修正 `2077056 -> 2078056`）",
          "- `SDS-2082.csv`: 20 题（从 `.doc` 文本提取）",
          "- `SAS-2083.csv`: 20 题（从 `.docx` 文本提取）",
          "- `SQA-2084.csv`: 29 题（由 SAQ 文档提取，命名统一为 SQA）",
          "",
          "注意：所有题干均来自你提供的原始文件，不做人工改写。",
      ]),
      encoding="utf-8",
  )


def main() -> None:
  build_epq()
  build_scl90()
  build_sds()
  build_sas()
  build_sqa()
  write_report()
  print(f"Generated question bank files in: {OUT_DIR}")


if __name__ == "__main__":
  main()
