from __future__ import annotations

import argparse
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
	sys.path.insert(0, str(PROJECT_ROOT))

from utils.excel_util import ExcelUtil
from utils.spire_word_util import SpireWordDoc


EXCEL_SUFFIXES = {".xlsx", ".xlsm"}
WORD_SUFFIXES = {".doc", ".docx"}
HEADER_KEYWORDS = {
	"key_points",
	"chunk_id",
	"original_text",
	"details",
	"suggest",
	"建议",
	"详情",
	"审查项",
	"原文",
	"合同原文",
	"文件块序号",
}


def _normalize_cell(value: object) -> str:
	if value is None:
		return ""
	if isinstance(value, float) and value.is_integer():
		return str(int(value))
	return str(value).strip()


def _looks_like_header(row: list[object]) -> bool:
	header_cells = {_normalize_cell(cell).lower() for cell in row[:6] if _normalize_cell(cell)}
	return bool(header_cells & HEADER_KEYWORDS)


def _parse_chunk_id(raw_value: object, *, chunk_id_base: int, row_number: int, excel_path: Path) -> int:
	raw_text = _normalize_cell(raw_value)
	if not raw_text:
		raise ValueError(f"{excel_path.name} 第 {row_number} 行缺少 chunk_id")

	try:
		chunk_id = int(float(raw_text))
	except ValueError as exc:
		raise ValueError(
			f"{excel_path.name} 第 {row_number} 行 chunk_id 无法解析为整数: {raw_text}"
		) from exc

	normalized_chunk_id = chunk_id - chunk_id_base
	if normalized_chunk_id < 0:
		raise ValueError(
			f"{excel_path.name} 第 {row_number} 行 chunk_id 越界: {chunk_id}"
		)
	return normalized_chunk_id


def load_comments_from_excel(
	excel_path: Path,
	*,
	sheet_name: str | None = None,
	chunk_id_base: int = 1,
	skip_header: bool = True,
) -> list[dict[str, object]]:
	rows = ExcelUtil.load_excel(excel_path, sheet_name=sheet_name, has_header=False)

	comments: list[dict[str, object]] = []
	for row_number, raw_row in enumerate(rows, start=1):
		if not isinstance(raw_row, list):
			continue

		row = list(raw_row)
		if not any(_normalize_cell(cell) for cell in row):
			continue
		if skip_header and row_number == 1 and _looks_like_header(row):
			continue

		padded = row + [None] * max(0, 6 - len(row))
		key_points = _normalize_cell(padded[0])
		original_text = _normalize_cell(padded[2])
		details = _normalize_cell(padded[3])
		suggest = _normalize_cell(padded[5])

		if not key_points and not original_text and not suggest:
			continue

		chunk_id = _parse_chunk_id(
			padded[1],
			chunk_id_base=chunk_id_base,
			row_number=row_number,
			excel_path=excel_path,
		)

		comments.append(
			{
				"id": f"{excel_path.stem}-{row_number}",
				"key_points": key_points,
				"chunk_id": chunk_id,
				"original_text": original_text,
				"details": details,
				"result": "不合格",
				"suggest": suggest,
			}
		)

	return comments



def _build_doc_index(doc_dir: Path) -> dict[str, Path]:
	index: dict[str, Path] = {}
	for path in sorted(doc_dir.iterdir()):
		if not path.is_file() or path.suffix.lower() not in WORD_SUFFIXES:
			continue
		index.setdefault(path.stem, path)
	return index


def _match_doc_path(excel_path: Path, doc_index: dict[str, Path]) -> Path | None:
	return doc_index.get(excel_path.stem)


def _close_spire_doc(doc: SpireWordDoc) -> None:
	internal_doc = getattr(doc, "_doc", None)
	if internal_doc is None:
		return
	try:
		internal_doc.Close()
	except Exception:
		pass


def clear_comments(doc: SpireWordDoc) -> None:
	internal_doc = getattr(doc, "_doc", None)
	if internal_doc is None:
		return
	try:
		internal_doc.Comments.Clear()
	except Exception:
		pass


def annotate_doc_from_excel(
	excel_path: Path,
	doc_path: Path,
	output_path: Path,
	*,
	sheet_name: str | None = None,
	chunk_id_base: int = 1,
	skip_header: bool = True,
) -> int:
	comments = load_comments_from_excel(
		excel_path,
		sheet_name=sheet_name,
		chunk_id_base=chunk_id_base,
		skip_header=skip_header,
	)

	doc = SpireWordDoc()
	try:
		doc.load(str(doc_path))
		clear_comments(doc)
		grouped_comments: dict[int, list[dict[str, object]]] = {}
		for comment in comments:
			chunk_id = int(comment["chunk_id"])
			if chunk_id >= doc.get_chunk_num():
				raise ValueError(
					f"{excel_path.name} 中的 chunk_id {chunk_id + chunk_id_base} 超出文档块数量 {doc.get_chunk_num()}"
				)
			grouped_comments.setdefault(chunk_id, []).append(comment)

		for chunk_id, chunk_comments in grouped_comments.items():
			doc.add_chunk_comment(chunk_id, chunk_comments)

		output_path.parent.mkdir(parents=True, exist_ok=True)
		doc.to_file(str(output_path), remove_prefix=True)
		return len(comments)
	finally:
		_close_spire_doc(doc)


def batch_annotate_docs(
	excel_dir: Path,
	output_dir: Path,
	*,
	sheet_name: str | None = None,
	chunk_id_base: int = 1,
	skip_header: bool = True,
) -> None:
	excel_dir = excel_dir.resolve()
	output_dir = output_dir.resolve()

	output_dir.mkdir(parents=True, exist_ok=True)
	doc_index = _build_doc_index(excel_dir)

	processed_files = 0
	skipped_files = 0
	total_comments = 0

	for excel_path in sorted(excel_dir.iterdir()):
		if not excel_path.is_file() or excel_path.suffix.lower() not in EXCEL_SUFFIXES:
			continue

		doc_path = _match_doc_path(excel_path, doc_index)
		if doc_path is None:
			skipped_files += 1
			print(f"Skip {excel_path.name}: 未找到同名 Word 文件")
			continue

		output_path = output_dir / doc_path.name
		comment_count = annotate_doc_from_excel(
			excel_path=excel_path,
			doc_path=doc_path,
			output_path=output_path,
			sheet_name=sheet_name,
			chunk_id_base=chunk_id_base,
			skip_header=skip_header,
		)
		processed_files += 1
		total_comments += comment_count
		print(
			f"Processed {excel_path.name} -> {output_path.name} | comments: {comment_count}"
		)

	print(
		f"Done: processed {processed_files} files, skipped {skipped_files} files, total comments {total_comments}"
	)


def _parse_args() -> argparse.Namespace:
	base = Path(__file__).parent
	parser = argparse.ArgumentParser(
		description="Read Excel comments in bulk and write them into Word documents."
	)
	parser.add_argument(
		"--excel-dir",
		type=Path,
		required=True,
		help="Directory containing Excel files.",
	)
	parser.add_argument(
		"--output-dir",
		type=Path,
		default=base / "results" / "re_comment_output",
		help="Directory to write annotated Word files.",
	)
	parser.add_argument(
		"--chunk-id-base",
		type=int,
		choices=(0, 1),
		default=0,
		help="Whether chunk_id in Excel is 0-based or 1-based. Defaults to 0.",
	)
	parser.add_argument(
		"--no-skip-header",
		action="store_true",
		help="Do not auto-skip the first row when it looks like a header.",
	)
	return parser.parse_args()


if __name__ == "__main__":
	args = _parse_args()
	batch_annotate_docs(
		excel_dir=args.excel_dir,
		output_dir=args.output_dir,
		chunk_id_base=args.chunk_id_base,
		skip_header=not args.no_skip_header,
	)
