from __future__ import annotations

from pathlib import Path

from ablate.assertions.run import run_assertions
from ablate.assertions.types import evaluate
from ablate.gitutil import current_commit
from ablate.task import Assertion, Task


def _task(assertions: list[Assertion], base_commit: str) -> Task:
    return Task(id="t", prompt="do it", base_commit=base_commit, **{"assert": assertions})


def test_exit_code_pass_and_fail(git_repo: Path):
    base = current_commit(git_repo)
    ok = Assertion(id="ok", type="exit_code", run="exit 0", expect=0)
    bad = Assertion(id="bad", type="exit_code", run="exit 1", expect=0)
    assert evaluate(ok, git_repo, base).passed is True
    result = evaluate(bad, git_repo, base)
    assert result.passed is False
    assert "exit 1" in result.detail


def test_exit_code_respects_custom_expect(git_repo: Path):
    base = current_commit(git_repo)
    a = Assertion(id="a", type="exit_code", run="exit 3", expect=3)
    assert evaluate(a, git_repo, base).passed is True


def test_lint_is_exit_code_shaped(git_repo: Path):
    base = current_commit(git_repo)
    a = Assertion(id="a", type="lint", run="exit 0")
    assert evaluate(a, git_repo, base).passed is True


def test_files_touched_must_include_glob_double_star(git_repo: Path):
    base = current_commit(git_repo)
    (git_repo / "tests").mkdir()
    (git_repo / "tests" / "sub").mkdir()
    (git_repo / "tests" / "sub" / "test_x.py").write_text("def test_x(): pass\n")

    a = Assertion(id="a", type="files_touched", must_include=["tests/**"])
    assert evaluate(a, git_repo, base).passed is True


def test_files_touched_missing_required_path_fails(git_repo: Path):
    base = current_commit(git_repo)
    a = Assertion(id="a", type="files_touched", must_include=["tests/**"])
    result = evaluate(a, git_repo, base)
    assert result.passed is False
    assert "missing required paths" in result.detail


def test_files_touched_must_not_include_catches_scope_creep(git_repo: Path):
    base = current_commit(git_repo)
    (git_repo / "vendor").mkdir()
    (git_repo / "vendor" / "lib.py").write_text("# vendored\n")

    a = Assertion(id="a", type="files_touched", must_not_include=["vendor/**"])
    result = evaluate(a, git_repo, base)
    assert result.passed is False
    assert "vendor/lib.py" in result.detail


def test_glob_star_does_not_cross_directory_boundary(git_repo: Path):
    base = current_commit(git_repo)
    (git_repo / "a").mkdir()
    (git_repo / "a" / "b").mkdir()
    (git_repo / "a" / "b" / "c.py").write_text("x = 1\n")

    # "a/*" (single star) must NOT match "a/b/c.py" -- only "**" crosses "/".
    a = Assertion(id="a", type="files_touched", must_include=["a/*"])
    result = evaluate(a, git_repo, base)
    assert result.passed is False


def test_present_finds_pattern_in_diff(git_repo: Path):
    base = current_commit(git_repo)
    (git_repo / "src.py").write_text("def add(a, b):\n    return a + b  # snake_case_ok\n")

    a = Assertion(id="a", type="present", pattern=r"snake_case_ok")
    assert evaluate(a, git_repo, base).passed is True


def test_absent_fails_when_pattern_found(git_repo: Path):
    base = current_commit(git_repo)
    (git_repo / "src.py").write_text("try:\n    pass\nexcept:\n    pass\n")

    a = Assertion(id="a", type="absent", pattern=r"except\s*:")
    result = evaluate(a, git_repo, base)
    assert result.passed is False


def test_present_absent_in_files_mode(git_repo: Path):
    base = current_commit(git_repo)
    a = Assertion(id="a", type="present", pattern=r"snake_case", paths=["*.md"], **{"in": "files"})
    assert evaluate(a, git_repo, base).passed is True


def test_script_runs_with_ablate_env_vars(git_repo: Path):
    base = current_commit(git_repo)
    script = (
        'python3 -c "import os,sys; '
        'sys.exit(0 if os.environ.get(\'ABLATE_TRIAL_DIR\') else 1)"'
    )
    a = Assertion(id="a", type="script", run=script, expect=0)
    assert evaluate(a, git_repo, base).passed is True


def test_script_capture_json_populates_value(git_repo: Path):
    base = current_commit(git_repo)
    a = Assertion(
        id="a", type="script", run='python3 -c "print(\'{\\\"n\\\": 7}\')"',
        expect=0, capture_json=True,
    )
    result = evaluate(a, git_repo, base)
    assert result.passed is True
    assert result.value == {"n": 7}


def test_script_capture_json_bad_output_fails(git_repo: Path):
    base = current_commit(git_repo)
    a = Assertion(id="a", type="script", run='echo "not json"', expect=0, capture_json=True)
    result = evaluate(a, git_repo, base)
    assert result.passed is False


def test_run_assertions_required_false_excluded_from_rollup(git_repo: Path):
    base = current_commit(git_repo)
    passing = Assertion(id="ok", type="exit_code", run="exit 0")
    failing_optional = Assertion(id="opt", type="exit_code", run="exit 1", required=False)
    task = _task([passing, failing_optional], base)

    results, passed = run_assertions(task, git_repo)

    assert passed is True
    assert {r.id: r.passed for r in results} == {"ok": True, "opt": False}


def test_run_assertions_required_true_fails_rollup(git_repo: Path):
    base = current_commit(git_repo)
    passing = Assertion(id="ok", type="exit_code", run="exit 0")
    failing = Assertion(id="bad", type="exit_code", run="exit 1")
    task = _task([passing, failing], base)

    _, passed = run_assertions(task, git_repo)

    assert passed is False
