# -*- coding: utf-8 -*-
"""CommitAttributionService Git 提交归因测试（Phase 7 Tier 1 BF）。

借鉴 zhikuncode-main CommitAttributionService.java：
- git blame 解析（行级作者 + 时间）
- git log 解析（最近 N 条提交）
- git shortlog 解析（按作者统计提交数）
- 提交摘要（commit + author + date + message 首行）
"""
import os
import subprocess
from pathlib import Path

import pytest

from abyss.vcs.commit_attribution import (
    CommitAttributionService,
    BlameLine,
    CommitInfo,
    AuthorStats,
    get_default_service,
)


@pytest.fixture
def git_repo(tmp_path: Path) -> str:
    """创建临时 git 仓库并写入若干提交。"""
    repo = str(tmp_path / "repo")
    os.makedirs(repo, exist_ok=True)
    env = os.environ.copy()
    env["GIT_AUTHOR_NAME"] = "Tester1"
    env["GIT_AUTHOR_EMAIL"] = "tester1@example.com"
    env["GIT_COMMITTER_NAME"] = "Tester1"
    env["GIT_COMMITTER_EMAIL"] = "tester1@example.com"
    env["GIT_AUTHOR_DATE"] = "2025-01-01T10:00:00"
    env["GIT_COMMITTER_DATE"] = "2025-01-01T10:00:00"

    def _git(*args):
        return subprocess.run(
            ["git", *args], cwd=repo, capture_output=True, text=True,
            env=env, timeout=10,
        )

    _git("init", "-q")
    _git("config", "user.name", "Tester1")
    _git("config", "user.email", "tester1@example.com")

    # 第一次提交
    (Path(repo) / "file.py").write_text("line1\nline2\nline3\n", encoding="utf-8")
    _git("add", "file.py")
    _git("commit", "-q", "-m", "first commit")

    # 第二次提交（不同作者）
    env["GIT_AUTHOR_NAME"] = "Tester2"
    env["GIT_AUTHOR_EMAIL"] = "tester2@example.com"
    env["GIT_COMMITTER_NAME"] = "Tester2"
    env["GIT_COMMITTER_EMAIL"] = "tester2@example.com"
    env["GIT_AUTHOR_DATE"] = "2025-01-02T11:00:00"
    env["GIT_COMMITTER_DATE"] = "2025-01-02T11:00:00"
    (Path(repo) / "file.py").write_text("line1\nline2 modified\nline3\n", encoding="utf-8")
    _git("add", "file.py")
    _git("commit", "-q", "-m", "second commit")

    return repo


@pytest.fixture
def service(git_repo: str) -> CommitAttributionService:
    return CommitAttributionService(working_dir=git_repo)


class TestBlameParse:
    """git blame 解析测试。"""

    def test_blame_returns_lines(self, service, git_repo):
        """blame 应返回每行的作者信息。"""
        result = service.blame("file.py")
        assert len(result) == 3
        for line in result:
            assert isinstance(line, BlameLine)
            assert line.line_number >= 1
            assert line.author in ("Tester1", "Tester2")
            assert line.commit_hash  # 40 字符 SHA

    def test_blame_specific_lines(self, service, git_repo):
        """指定行范围应只返回该范围。"""
        result = service.blame("file.py", start=1, end=2)
        assert len(result) == 2
        assert result[0].line_number == 1
        assert result[1].line_number == 2

    def test_blame_modified_line_author(self, service, git_repo):
        """第二行被 Tester2 修改，作者应是 Tester2。"""
        result = service.blame("file.py")
        # line2 被第二次提交修改
        line2 = next(l for l in result if l.line_number == 2)
        assert line2.author == "Tester2"

    def test_blame_nonexistent_file_returns_empty(self, service, git_repo):
        """blame 不存在的文件应返回空列表，不抛异常。"""
        result = service.blame("nonexistent.py")
        assert result == []


class TestLogParse:
    """git log 解析测试。"""

    def test_log_returns_recent_commits(self, service, git_repo):
        """log 默认返回最近 10 条提交。"""
        commits = service.log(limit=10)
        assert len(commits) == 2  # 仓库里只有 2 个提交
        for c in commits:
            assert isinstance(c, CommitInfo)
            assert c.hash
            assert c.author
            assert c.date
            assert c.message

    def test_log_returns_in_reverse_chronological(self, service, git_repo):
        """log 应按时间倒序（最近在前）。"""
        commits = service.log(limit=10)
        assert "second" in commits[0].message
        assert "first" in commits[1].message

    def test_log_limit_1(self, service, git_repo):
        """limit=1 应只返回最近一条。"""
        commits = service.log(limit=1)
        assert len(commits) == 1
        assert "second" in commits[0].message


class TestAuthorStats:
    """按作者统计提交数测试。"""

    def test_shortlog_groups_by_author(self, service, git_repo):
        """按作者分组统计提交数。"""
        stats = service.author_stats()
        assert len(stats) == 2  # Tester1 + Tester2
        authors = {s.author for s in stats}
        assert "Tester1" in authors
        assert "Tester2" in authors
        for s in stats:
            assert s.commit_count == 1


class TestCommitSummary:
    """提交摘要测试。"""

    def test_commit_summary_returns_full_info(self, service, git_repo):
        """获取指定 commit 摘要。"""
        commits = service.log(limit=1)
        sha = commits[0].hash
        summary = service.commit_summary(sha)
        assert summary is not None
        assert summary.hash == sha
        assert summary.author == "Tester2"
        assert "second" in summary.message

    def test_commit_summary_nonexistent_returns_none(self, service, git_repo):
        """不存在的 commit 返回 None。"""
        assert service.commit_summary("nonexistent_sha") is None


class TestNonGitRepo:
    """非 git 仓库场景。"""

    def test_blame_in_non_git_returns_empty(self, tmp_path):
        svc = CommitAttributionService(working_dir=str(tmp_path))
        assert svc.blame("any.py") == []

    def test_log_in_non_git_returns_empty(self, tmp_path):
        svc = CommitAttributionService(working_dir=str(tmp_path))
        assert svc.log() == []

    def test_author_stats_in_non_git_returns_empty(self, tmp_path):
        svc = CommitAttributionService(working_dir=str(tmp_path))
        assert svc.author_stats() == []

    def test_is_git_repo_detects_git_directory(self, git_repo, tmp_path):
        svc = CommitAttributionService(working_dir=git_repo)
        assert svc.is_git_repo() is True
        svc2 = CommitAttributionService(working_dir=str(tmp_path))
        assert svc2.is_git_repo() is False


class TestDefaultService:
    """模块级单例测试。"""

    def test_get_default_returns_same_instance(self):
        s1 = get_default_service()
        s2 = get_default_service()
        assert s1 is s2
