import argparse
from datetime import datetime
from pathlib import Path
import re

from openpyxl import Workbook
from openpyxl.styles import Alignment, Font


REPO_ROOT = Path(__file__).resolve().parent.parent
DEFAULT_INPUT_DIR = REPO_ROOT / "output" / "test_cases"
DEFAULT_OUTPUT_DIR = REPO_ROOT / "output" / "excel_reports"
REQUIRED_COLUMNS = [
    "用例编号",
    "模块",
    "用例标题",
    "优先级",
    "类型",
    "前置条件",
    "测试步骤",
    "测试数据",
    "预期结果",
    "备注",
]
LEGACY_COLUMNS = [
    "用例编号",
    "模块",
    "用例标题",
    "优先级",
    "前置条件",
    "测试步骤",
    "测试数据",
    "预期结果",
    "备注",
]
SUPPORTED_COLUMN_SETS = [REQUIRED_COLUMNS, LEGACY_COLUMNS]
YUNXIAO_HEADERS = [
    "标题",
    "编号",
    "目录",
    "创建时间",
    "前置条件",
    "步骤描述",
    "预期结果",
    "优先级",
    "类型",
    "URL",
]
TYPE_ENUMS = [
    "功能测试",
    "性能测试",
    "兼容性测试",
    "易用性测试",
    "安全性测试",
    "冒烟测试",
    "回归测试",
    "其他",
]
SEPARATOR_RE = re.compile(r"^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$")
MODULE_PATH_SEPARATOR_RE = re.compile(r"\s*[-－—–]\s*")
BR_TAG_RE = re.compile(r"<br\s*/?>", re.IGNORECASE)


def headers_supported(headers: list[str]) -> bool:
    return headers in SUPPORTED_COLUMN_SETS


def find_latest_markdown_file(directory: Path) -> Path | None:
    files = sorted(directory.glob("*.md"), key=lambda item: item.stat().st_mtime)
    return files[-1] if files else None


def split_markdown_row(line: str) -> list[str]:
    stripped = line.strip()
    if stripped.startswith("|"):
        stripped = stripped[1:]
    if stripped.endswith("|"):
        stripped = stripped[:-1]

    cells: list[str] = []
    current: list[str] = []
    escaped = False

    for char in stripped:
        if escaped:
            current.append(char)
            escaped = False
            continue
        if char == "\\":
            escaped = True
            continue
        if char == "|":
            cells.append("".join(current).strip())
            current = []
            continue
        current.append(char)

    if escaped:
        current.append("\\")

    cells.append("".join(current).strip())
    return cells


def normalize_module_path(value: str) -> str:
    stripped = value.strip()
    if not stripped:
        return stripped

    if "|" in stripped:
        parts = [part.strip() for part in stripped.split("|") if part.strip()]
        return "|".join(parts)

    if MODULE_PATH_SEPARATOR_RE.search(stripped):
        parts = [part.strip() for part in MODULE_PATH_SEPARATOR_RE.split(stripped) if part.strip()]
        if len(parts) > 1:
            return "|".join(parts)

    return stripped


def load_markdown_table(source_file: Path) -> tuple[list[str], list[list[str]]]:
    content = source_file.read_text(encoding="utf-8")
    table_lines: list[str] = []

    for line in content.splitlines():
        if line.strip().startswith("|"):
            table_lines.append(line.rstrip())
        elif table_lines:
            break

    if len(table_lines) < 3:
        raise ValueError(f"未在 {source_file} 中找到有效的 Markdown 表格。")

    headers = split_markdown_row(table_lines[0])
    if not SEPARATOR_RE.match(table_lines[1].strip()):
        raise ValueError(f"{source_file} 的第二行不是合法的 Markdown 表头分隔线。")

    module_index = headers.index("模块") if "模块" in headers else None

    rows: list[list[str]] = []
    for line in table_lines[2:]:
        row = split_markdown_row(line)
        if len(row) != len(headers):
            raise ValueError(
                f"{source_file} 中存在列数不一致的行，表头 {len(headers)} 列，实际 {len(row)} 列。"
            )
        if module_index is not None:
            row[module_index] = normalize_module_path(row[module_index])
        rows.append(row)

    if not rows:
        raise ValueError(f"{source_file} 的 Markdown 表格没有数据行。")

    return headers, rows


def infer_case_type(case: dict[str, str]) -> str:
    explicit = case.get("类型", "").strip()
    if explicit in TYPE_ENUMS:
        return explicit

    combined = " ".join(
        [
            case.get("用例标题", ""),
            case.get("模块", ""),
            case.get("备注", ""),
            case.get("测试步骤", ""),
            case.get("预期结果", ""),
        ]
    )

    if re.search(r"性能|RT|响应时间|P95|吞吐|压测|并发量", combined, re.IGNORECASE):
        return "性能测试"
    if re.search(r"越权|鉴权|权限|抓包|篡改|注入|XSS|CSRF|SQL", combined, re.IGNORECASE):
        return "安全性测试"
    if re.search(r"兼容|浏览器|小程序|H5|多端", combined, re.IGNORECASE):
        return "兼容性测试"
    if re.search(r"易用|交互|文案|可读|展示", combined, re.IGNORECASE):
        return "易用性测试"
    if re.search(r"冒烟|主流程", combined, re.IGNORECASE):
        return "冒烟测试"
    if re.search(r"历史缺陷|回归|AI修正", combined, re.IGNORECASE):
        return "回归测试"
    return "功能测试"


def normalize_multiline_text(value: str) -> str:
    text = BR_TAG_RE.sub("\n", value or "")
    return text.replace("\\n", "\n").strip()


def normalize_topic_name(text: str) -> str:
    normalized = text.strip()
    changed = True
    while changed:
        changed = False
        for token in ("测试用例", "需求", "分析"):
            if normalized.endswith(token):
                normalized = normalized[: -len(token)].strip()
                changed = True
        normalized = re.sub(r"[_\-\s|]+$", "", normalized)
    return normalized or text.strip()


def build_directory_name(source_path: Path, module_value: str) -> str:
    topic = normalize_topic_name(source_path.stem)
    module = normalize_module_path(module_value)
    if not module:
        return topic
    if module.startswith(topic):
        return module
    return f"{topic}|{module}"


def normalize_case_rows(headers: list[str], rows: list[list[str]]) -> list[dict[str, str]]:
    cases: list[dict[str, str]] = []
    for row in rows:
        current = {header: (row[index] or "").strip() for index, header in enumerate(headers)}
        if "类型" not in current:
            current["类型"] = infer_case_type(current)
        elif current["类型"] not in TYPE_ENUMS:
            current["类型"] = infer_case_type(current)
        cases.append(current)
    return cases


def build_yunxiao_rows(source_path: Path, headers: list[str], rows: list[list[str]]) -> list[list[object]]:
    created_time = datetime.fromtimestamp(source_path.stat().st_mtime).replace(microsecond=0)
    cases = normalize_case_rows(headers, rows)
    output_rows: list[list[object]] = []

    for case in cases:
        steps_text = normalize_multiline_text(case.get("测试步骤", ""))
        data_text = normalize_multiline_text(case.get("测试数据", ""))
        if data_text:
            steps_text = f"{steps_text}\n[测试数据]\n{data_text}" if steps_text else f"[测试数据]\n{data_text}"

        output_rows.append(
            [
                case.get("用例标题", ""),
                case.get("用例编号", ""),
                build_directory_name(source_path, case.get("模块", "")),
                created_time,
                normalize_multiline_text(case.get("前置条件", "")),
                steps_text,
                normalize_multiline_text(case.get("预期结果", "")),
                case.get("优先级", ""),
                infer_case_type(case),
                "",
            ]
        )

    return output_rows


def resolve_source_file(source_file: str | None, base_name: str | None, input_dir: Path) -> Path:
    if source_file:
        path = Path(source_file)
        return path if path.is_absolute() else REPO_ROOT / path

    if base_name:
        return input_dir / f"{base_name}_测试用例.md"

    latest = find_latest_markdown_file(input_dir)
    if latest is None:
        raise FileNotFoundError(f"在 {input_dir} 下没有找到任何 Markdown 测试用例文件。")
    return latest


def build_current_excel_path(source_path: Path, output_dir: Path) -> Path:
    return output_dir / f"{source_path.stem}.xlsx"


def export_markdown_to_excel(
    source_path: Path,
    output_dir: Path,
    output_path: Path | None = None,
) -> Path:
    headers, rows = load_markdown_table(source_path)
    if not headers_supported(headers):
        raise ValueError(
            "测试用例表头不符合支持格式。\n"
            f"支持: {REQUIRED_COLUMNS} 或 {LEGACY_COLUMNS}\n"
            f"实际: {headers}"
        )
    output_dir.mkdir(parents=True, exist_ok=True)
    if output_path is None:
        output_path = build_current_excel_path(source_path, output_dir)

    workbook = Workbook()
    worksheet = workbook.active
    worksheet.title = "testcase items"

    worksheet.append(YUNXIAO_HEADERS)
    for row in build_yunxiao_rows(source_path, headers, rows):
        worksheet.append(row)

    header_font = Font(bold=True)
    wrap_alignment = Alignment(vertical="top", wrap_text=True)

    for cell in worksheet[1]:
        cell.font = header_font
        cell.alignment = wrap_alignment

    for row in worksheet.iter_rows(min_row=2):
        for cell in row:
            cell.alignment = wrap_alignment

    worksheet.freeze_panes = "A2"
    worksheet.auto_filter.ref = f"A1:J{worksheet.max_row}"

    for column_cells in worksheet.columns:
        max_length = max(len(str(cell.value or "")) for cell in column_cells)
        adjusted_width = min(max_length + 4, 60)
        worksheet.column_dimensions[column_cells[0].column_letter].width = adjusted_width

    workbook.save(output_path)
    return output_path


def main() -> None:
    parser = argparse.ArgumentParser(description="将 Markdown 测试用例表格导出为 Excel。")
    parser.add_argument("--source-file", help="指定测试用例 Markdown 文件路径。")
    parser.add_argument("--base-name", help="按 BASE_NAME 查找 output/test_cases/{BASE_NAME}_测试用例.md。")
    parser.add_argument(
        "--input-dir",
        default=str(DEFAULT_INPUT_DIR),
        help="测试用例 Markdown 所在目录，默认 output/test_cases。",
    )
    parser.add_argument(
        "--output-dir",
        default=str(DEFAULT_OUTPUT_DIR),
        help="Excel 输出目录，默认 output/excel_reports。",
    )
    args = parser.parse_args()

    input_dir = Path(args.input_dir)
    if not input_dir.is_absolute():
        input_dir = REPO_ROOT / input_dir

    output_dir = Path(args.output_dir)
    if not output_dir.is_absolute():
        output_dir = REPO_ROOT / output_dir

    source_path = resolve_source_file(args.source_file, args.base_name, input_dir)
    if not source_path.exists():
        raise FileNotFoundError(f"测试用例文件不存在：{source_path}")

    output_path = export_markdown_to_excel(source_path, output_dir)
    headers, rows = load_markdown_table(source_path)
    print(f"📄 来源文件: {source_path}")
    print(f"🧩 Markdown 表头字段: {', '.join(headers)}")
    print(f"📊 用例数量: {len(rows)}")
    print(f"✅ Excel 已生成: {output_path}")


if __name__ == "__main__":
    main()
