# -*- coding: utf-8 -*-
"""
run_audit 主入口的单元测试。

验证：
- docs/audit/ 目录自动创建
- 三份报告文件正确生成
- 报告头部包含时间戳和仓库路径
- 目录创建失败时抛出 RuntimeError
"""

from __future__ import annotations

import os
import re
from pathlib import Path

import pytest


class TestEnsureReportDir:
    """测试 _ensure_report_dir 目录创建逻辑。"""

    def test_creates_dir_when_missing(self, tmp_path: Path):
        from scripts.audit.run_audit import _ensure_report_dir

        result = _ensure_report_dir(tmp_path)
        expected = tmp_path / "docs" / "audit" / "repo"
        assert result == expected
        assert expected.is_dir()

    def test_returns_existing_dir(self, tmp_path: Path):
        from scripts.audit.run_audit import _ensure_report_dir

        audit_dir = tmp_path / "docs" / "audit" / "repo"
        audit_dir.mkdir(parents=True)
        result = _ensure_report_dir(tmp_path)
        assert result == audit_dir

    def test_raises_on_creation_failure(self, tmp_path: Path):
        from scripts.audit.run_audit import _ensure_report_dir

        # 在 docs/audit 位置放一个文件，使 mkdir 失败
        docs = tmp_path / "docs"
        docs.mkdir()
        (docs / "audit").write_text("block", encoding="utf-8")

        with pytest.raises(RuntimeError, match="无法创建报告输出目录"):
            _ensure_report_dir(tmp_path)


class TestInjectHeader:
    """测试 _inject_header 兜底注入逻辑。"""

    def test_skips_when_header_present(self):
        from scripts.audit.run_audit import _inject_header

        report = "# 标题\n\n- 生成时间: 2025-01-01T00:00:00Z\n- 仓库路径: `/repo`\n"
        result = _inject_header(report, "2025-06-01T00:00:00Z", "/other")
        # 不应修改已有头部
        assert result == report

    def test_injects_when_header_missing(self):
        from scripts.audit.run_audit import _inject_header

        report = "# 无头部报告\n\n内容..."
        result = _inject_header(report, "2025-06-01T00:00:00Z", "/repo")
        assert "生成时间: 2025-06-01T00:00:00Z" in result
        assert "仓库路径: `/repo`" in result


class TestRunAudit:
    """测试 run_audit 完整流程（使用最小仓库结构）。"""

    def _make_minimal_repo(self, tmp_path: Path) -> Path:
        """构造一个最小仓库结构，足以让 run_audit 跑通。"""
        repo = tmp_path / "repo"
        repo.mkdir()

        # 核心代码目录
        cli_dir = repo / "cli"
        cli_dir.mkdir()
        (cli_dir / "__init__.py").write_text("", encoding="utf-8")
        (cli_dir / "main.py").write_text(
            "# -*- coding: utf-8 -*-\ndef main(): pass\n",
            encoding="utf-8",
        )

        # config 目录
        config_dir = repo / "config"
        config_dir.mkdir()
        (config_dir / "__init__.py").write_text("", encoding="utf-8")
        (config_dir / "defaults.py").write_text("DEFAULTS = {}\n", encoding="utf-8")

        # docs 目录
        docs_dir = repo / "docs"
        docs_dir.mkdir()
        (docs_dir / "README.md").write_text("# 文档\n", encoding="utf-8")

        # 根目录文件
        (repo / "README.md").write_text("# 项目\n", encoding="utf-8")

        return repo

    def test_creates_three_reports(self, tmp_path: Path):
        from scripts.audit.run_audit import run_audit

        repo = self._make_minimal_repo(tmp_path)
        run_audit(repo)

        audit_dir = repo / "docs" / "audit" / "repo"
        assert (audit_dir / "file_inventory.md").is_file()
        assert (audit_dir / "flow_tree.md").is_file()
        assert (audit_dir / "doc_alignment.md").is_file()

    def test_reports_contain_timestamp(self, tmp_path: Path):
        from scripts.audit.run_audit import run_audit

        repo = self._make_minimal_repo(tmp_path)
        run_audit(repo)

        audit_dir = repo / "docs" / "audit" / "repo"
        # ISO 时间戳格式
        ts_pattern = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z")

        for name in ("file_inventory.md", "flow_tree.md", "doc_alignment.md"):
            content = (audit_dir / name).read_text(encoding="utf-8")
            assert ts_pattern.search(content), f"{name} 缺少时间戳"

    def test_reports_contain_repo_path(self, tmp_path: Path):
        from scripts.audit.run_audit import run_audit

        repo = self._make_minimal_repo(tmp_path)
        run_audit(repo)

        audit_dir = repo / "docs" / "audit" / "repo"
        repo_str = str(repo.resolve())

        for name in ("file_inventory.md", "flow_tree.md", "doc_alignment.md"):
            content = (audit_dir / name).read_text(encoding="utf-8")
            assert repo_str in content, f"{name} 缺少仓库路径"

    def test_writes_only_to_docs_audit(self, tmp_path: Path):
        """验证所有写操作仅限 docs/audit/ 目录（Property 15）。"""
        from scripts.audit.run_audit import run_audit

        repo = self._make_minimal_repo(tmp_path)

        # 记录运行前的文件快照（排除 docs/audit/）
        before = set()
        for p in repo.rglob("*"):
            rel = p.relative_to(repo).as_posix()
            if not rel.startswith("docs/audit"):
                before.add((rel, p.stat().st_mtime if p.is_file() else None))

        run_audit(repo)

        # 运行后检查：docs/audit/ 外的文件不应被修改
        for p in repo.rglob("*"):
            rel = p.relative_to(repo).as_posix()
            if rel.startswith("docs/audit"):
                continue
            if p.is_file():
                # 文件应在之前的快照中
                found = any(r == rel for r, _ in before)
                assert found, f"意外创建了 docs/audit/ 外的文件: {rel}"

    def test_auto_creates_docs_audit_dir(self, tmp_path: Path):
        from scripts.audit.run_audit import run_audit

        repo = self._make_minimal_repo(tmp_path)
        # 确保 docs/audit/ 不存在
        audit_dir = repo / "docs" / "audit" / "repo"
        assert not audit_dir.exists()

        run_audit(repo)
        assert audit_dir.is_dir()
