import os
import sys
import subprocess
import pytest
import shutil
import tempfile
from pathlib import Path


def run_diff_collect(repo_root, base_commit="", env=None):
    script_path = Path(__file__).parent.parent / ".agents" / "skills" / "multi-agent-mux-loop" / "scripts" / "diff_collect.sh"
    cmd = ["bash", str(script_path), str(repo_root)]
    if base_commit:
        cmd.append(base_commit)
    run_env = dict(os.environ)
    if env:
        run_env.update(env)
    res = subprocess.run(cmd, capture_output=True, text=True, env=run_env)
    return res


@pytest.fixture
def b7_repo(tmp_path):
    repo = tmp_path / "b7_repo"
    repo.mkdir()
    subprocess.run(["git", "init"], cwd=str(repo), capture_output=True)
    subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=str(repo), capture_output=True)
    subprocess.run(["git", "config", "user.name", "Test User"], cwd=str(repo), capture_output=True)
    
    (repo / ".gitignore").write_text("ignored.log\n")
    (repo / "tracked.txt").write_text("line 1\n")
    subprocess.run(["git", "add", "."], cwd=str(repo), capture_output=True)
    subprocess.run(["git", "commit", "-m", "initial commit"], cwd=str(repo), capture_output=True)
    
    base_commit = subprocess.run(["git", "rev-parse", "HEAD"], cwd=str(repo), capture_output=True, text=True).stdout.strip()
    return repo, base_commit


def test_b7_1_untracked_new_file_content(b7_repo):
    repo, base_commit = b7_repo
    (repo / "newfile.py").write_text("print('hello')\n")
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "newfile.py" in res.stdout
    assert "print('hello')" in res.stdout


def test_b7_2_tracked_file_modification(b7_repo):
    repo, base_commit = b7_repo
    with open(repo / "tracked.txt", "a") as f:
        f.write("line 2\n")
        
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "tracked.txt" in res.stdout
    assert "+line 2" in res.stdout


def test_b7_3_gitignore_respected(b7_repo):
    repo, base_commit = b7_repo
    (repo / "ignored.log").write_text("secret log\n")
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "ignored.log" not in res.stdout


def test_b7_4_index_not_mutated(b7_repo):
    repo, base_commit = b7_repo
    (repo / "untracked.txt").write_text("untracked\n")
    
    run_diff_collect(repo, base_commit)
    
    status = subprocess.run(["git", "status", "--porcelain"], cwd=str(repo), capture_output=True, text=True).stdout
    assert "??" in status
    assert "A  untracked.txt" not in status


def test_b7_5_git_commit_am_safety(b7_repo):
    repo, base_commit = b7_repo
    (repo / "newfile.txt").write_text("new content\n")
    with open(repo / "tracked.txt", "a") as f:
        f.write("mod\n")
        
    run_diff_collect(repo, base_commit)
    
    subprocess.run(["git", "commit", "-am", "wip"], cwd=str(repo), capture_output=True)
    
    log_files = subprocess.run(["git", "show", "--stat", "HEAD"], cwd=str(repo), capture_output=True, text=True).stdout
    assert "tracked.txt" in log_files
    assert "newfile.txt" not in log_files


def test_b7_6_cwd_independence(b7_repo, tmp_path):
    repo, base_commit = b7_repo
    (repo / "new.py").write_text("x = 1\n")
    
    res_root = run_diff_collect(repo, base_commit)
    assert res_root.returncode == 0
    assert "new.py" in res_root.stdout
    
    outside = tmp_path / "outside"
    outside.mkdir()
    res_out = subprocess.run(
        ["bash", str(Path(__file__).parent.parent / ".agents" / "skills" / "multi-agent-mux-loop" / "scripts" / "diff_collect.sh"), str(repo), base_commit],
        capture_output=True, text=True, cwd=str(outside)
    )
    assert res_out.returncode == 0
    assert "new.py" in res_out.stdout


def test_b7_7_non_git_repo_fails_with_banner(tmp_path):
    non_repo = tmp_path / "not_git"
    non_repo.mkdir()
    
    res = run_diff_collect(non_repo)
    assert res.returncode == 2
    assert "!!! CHANGE SET UNAVAILABLE !!!" in res.stdout
    assert "reason:" in res.stdout


def test_b7_8_clean_repo_returns_no_changes(b7_repo):
    repo, base_commit = b7_repo
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "(no changes since base commit)" in res.stdout


def test_b7_9_truncation_limits(b7_repo):
    repo, base_commit = b7_repo
    large_text = "\n".join([f"line {i}" for i in range(100)])
    (repo / "big.txt").write_text(large_text)
    
    res = run_diff_collect(repo, base_commit, env={"MAM_DIFF_MAX_LINES": "50"})
    assert res.returncode == 0
    assert "!!! DIFF TRUNCATED !!!" in res.stdout
    assert "over the review limit" in res.stdout


def test_b7_10_within_limits_untruncated(b7_repo):
    repo, base_commit = b7_repo
    (repo / "small.txt").write_text("small\n")
    
    res = run_diff_collect(repo, base_commit, env={"MAM_DIFF_MAX_LINES": "4000"})
    assert res.returncode == 0
    assert "!!! DIFF TRUNCATED !!!" not in res.stdout
    assert "small" in res.stdout


def test_b7_11_filename_with_spaces(b7_repo):
    repo, base_commit = b7_repo
    (repo / "two words.py").write_text("val = 42\n")
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "two words.py" in res.stdout


def test_b7_12_run_loop_refuses_review_on_error(tmp_path):
    non_repo = tmp_path / "non_repo_dir"
    non_repo.mkdir()
    
    script_path = Path(__file__).parent.parent / ".agents" / "skills" / "multi-agent-mux-loop" / "scripts" / "diff_collect.sh"
    cmd_str = f"source '{script_path}' && mam_collect_changes_diff '{non_repo}'"
    res = subprocess.run(["bash", "-c", cmd_str], capture_output=True, text=True)
    
    assert res.returncode == 2
    assert "!!! CHANGE SET UNAVAILABLE !!!" in res.stdout
    assert "not a git repository" in res.stdout


def test_b7_13_single_collection_per_iteration():
    run_loop_path = Path(__file__).parent.parent / ".agents" / "skills" / "multi-agent-mux-loop" / "scripts" / "run_loop.sh"
    content = run_loop_path.read_text()
    
    assert "CHANGES_DIFF=$(mam_collect_changes_diff" in content
    # Ensure mam_collect_changes_diff is outside for rev in REVIEWERS loop
    outside_idx = content.find("CHANGES_DIFF=$(mam_collect_changes_diff")
    loop_idx = content.find("for rev in \"${REVIEWERS[@]}\"; do")
    assert outside_idx != -1
    assert loop_idx != -1
    assert outside_idx < loop_idx


def test_b7_14_bash_syntax_clean():
    base = Path(__file__).parent.parent / ".agents" / "skills" / "multi-agent-mux-loop" / "scripts"
    diff_script = base / "diff_collect.sh"
    loop_script = base / "run_loop.sh"
    
    res1 = subprocess.run(["bash", "-n", str(diff_script)], capture_output=True)
    res2 = subprocess.run(["bash", "-n", str(loop_script)], capture_output=True)
    
    assert res1.returncode == 0, res1.stderr
    assert res2.returncode == 0, res2.stderr


def test_b7_15_untracked_binary_summary(b7_repo):
    repo, base_commit = b7_repo
    (repo / "data.bin").write_bytes(bytes(range(256)) * 10)
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "Binary files" in res.stdout or "data.bin" in res.stdout
    assert "GIT binary patch" not in res.stdout


def test_b7_16_nested_git_repo_file_included(b7_repo):
    repo, base_commit = b7_repo
    nested = repo / "nested_repo"
    nested.mkdir()
    subprocess.run(["git", "init"], cwd=str(nested), capture_output=True)
    (nested / "nested_file.py").write_text("print('nested')\n")
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "nested_file.py" in res.stdout or "nested_repo" in res.stdout


def test_b7_17_nested_git_repo_gitignore_respected(b7_repo):
    repo, base_commit = b7_repo
    nested = repo / "nested_repo"
    nested.mkdir()
    subprocess.run(["git", "init"], cwd=str(nested), capture_output=True)
    subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=str(nested), capture_output=True)
    subprocess.run(["git", "config", "user.name", "Test User"], cwd=str(nested), capture_output=True)
    (nested / ".gitignore").write_text("nested_ignored.log\n")
    subprocess.run(["git", "add", ".gitignore"], cwd=str(nested), capture_output=True)
    subprocess.run(["git", "commit", "-m", "add ignore"], cwd=str(nested), capture_output=True)
    
    (nested / "nested_ignored.log").write_text("ignored\n")
    (nested / "nested_valid.py").write_text("valid\n")
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "nested_ignored.log" not in res.stdout


def test_b7_18_nested_git_marker_announced(b7_repo):
    repo, base_commit = b7_repo
    nested = repo / "nested_repo"
    nested.mkdir()
    subprocess.run(["git", "init"], cwd=str(nested), capture_output=True)
    (nested / "foo.py").write_text("bar\n")
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "!!! NOT EXPANDED IN THIS DIFF !!!" in res.stdout or "NESTED GIT REPOSITORY" in res.stdout


def test_b7_19_directory_symlink_announced(b7_repo):
    repo, base_commit = b7_repo
    target_dir = repo / "target_dir"
    target_dir.mkdir()
    (target_dir / "target_file.txt").write_text("data\n")
    
    symlink_path = repo / "link_dir"
    try:
        os.symlink("target_dir", str(symlink_path))
    except Exception:
        pytest.skip("Symlinks not supported")
        
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "symlink" in res.stdout.lower() or "NOT EXPANDED" in res.stdout or "link_dir" in res.stdout


def test_b7_20_plain_file_diff_preserved(b7_repo):
    repo, base_commit = b7_repo
    (repo / "plain.txt").write_text("plain content\n")
    
    res = run_diff_collect(repo, base_commit)
    assert res.returncode == 0
    assert "plain.txt" in res.stdout
    assert "+plain content" in res.stdout
