from __future__ import annotations

import json
import os
import subprocess
from pathlib import Path

from skills._shared.python.skill_testing import assert_entrypoints_exist, assert_skill_contract


def skill_root() -> Path:
    return Path(__file__).resolve().parents[1]


def repo_root() -> Path:
    if env := os.environ.get("AGENT_HOME"):
        candidate = Path(env)
        if candidate.is_dir():
            return candidate.resolve()
    return Path(__file__).resolve().parents[5]


def script_path() -> Path:
    return skill_root() / "scripts" / "review_specialists.py"


def run_script(*args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
    env = os.environ.copy()
    env["AGENT_HOME"] = str(repo_root())
    return subprocess.run(
        [str(script_path()), *args],
        cwd=str(cwd or repo_root()),
        text=True,
        capture_output=True,
        env=env,
        check=False,
    )


def git(repo: Path, *args: str) -> None:
    subprocess.run(["git", "-C", str(repo), *args], text=True, capture_output=True, check=True)


def init_fixture_repo(tmp_path: Path) -> Path:
    repo = tmp_path / "repo"
    repo.mkdir()
    git(repo, "init")
    git(repo, "config", "user.email", "agent-kit@example.invalid")
    git(repo, "config", "user.name", "Agent Kit Tests")
    (repo / "README.md").write_text("baseline\n", encoding="utf-8")
    git(repo, "add", "README.md")
    git(repo, "commit", "-m", "baseline")
    return repo


def write_lines(repo: Path, rel: str, count: int) -> None:
    path = repo / rel
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text("\n".join(f"line {index}" for index in range(count)) + "\n", encoding="utf-8")
    git(repo, "add", rel)


def scope_payload(repo: Path, *extra: str) -> dict[str, object]:
    result = run_script("scope", "--repo", str(repo), "--base", "HEAD", *extra)
    assert result.returncode == 0, result.stderr
    return json.loads(result.stdout)


def test_workflows_code_review_specialists_contract() -> None:
    assert_skill_contract(skill_root())


def test_workflows_code_review_specialists_entrypoints_exist() -> None:
    assert_entrypoints_exist(skill_root(), ["scripts/review_specialists.py"])


def test_skill_documents_read_only_and_delegation_boundaries() -> None:
    text = (skill_root() / "SKILL.md").read_text(encoding="utf-8")
    normalized = " ".join(text.split())

    assert "Keep this workflow read-only" in text
    assert "does not auto-fix code, merge, close PRs, or post live PR comments" in normalized
    assert "Use explicit user instruction or a delegation mode" in text
    assert "dispatch-pr-review" in text
    assert "review-evidence" in text
    assert "diff_lines < 50" in text
    assert "--all-specialists" in text


def test_specialist_references_exist_and_use_agent_kit_schema() -> None:
    specialists = {
        "api-contract",
        "data-migration",
        "maintainability",
        "performance",
        "red-team",
        "security",
        "testing",
    }
    specialist_dir = skill_root() / "references" / "specialists"
    found = {path.stem for path in specialist_dir.glob("*.md")}
    assert found == specialists

    for name in specialists:
        text = (specialist_dir / f"{name}.md").read_text(encoding="utf-8")
        normalized = " ".join(text.split())
        assert "## Activation Scope" in text
        assert "## Required Output Shape" in text
        assert "## Evidence Expectations" in text
        assert "## No Findings Behavior" in text
        assert "critical|high|medium|low|info" in text
        assert "hidden home-state paths" in normalized
        assert "merge decisions" in normalized

    red_team = (specialist_dir / "red-team.md").read_text(encoding="utf-8")
    assert "Run after the other selected specialists" in red_team
    assert "receives the merged findings" in red_team


def test_shared_contract_and_report_template_are_present() -> None:
    contract = (skill_root() / "references" / "SPECIALIST_REVIEW_CONTRACT.md").read_text(encoding="utf-8")
    template = (skill_root() / "references" / "SPECIALIST_REVIEW_REPORT_TEMPLATE.md").read_text(encoding="utf-8")

    assert '"severity": "high"' in contract
    assert "CRITICAL` -> `critical" in contract
    assert "INFORMATIONAL` -> `info" in contract
    assert "Numeric PR quality scoring is not adopted in v1" in contract
    for heading in (
        "## Scope",
        "## Specialist Dispatch",
        "## Findings",
        "## Red Team",
        "## Evidence Reviewed",
        "## Residual Risk",
        "## Recommended Next Step",
    ):
        assert heading in template
    assert "numeric PR quality" not in template.lower()


def test_scope_docs_only_small_diff_skips_specialists(tmp_path: Path) -> None:
    repo = init_fixture_repo(tmp_path)
    write_lines(repo, "docs/usage.md", 10)

    payload = scope_payload(repo)

    assert payload["diff_lines"] == 10
    assert payload["changed_files"] == ["docs/usage.md"]
    assert payload["stack"] == ["docs"]
    assert payload["small_diff_skip"] is True
    assert payload["suggested_specialists"] == []


def test_scope_backend_api_auth_and_migration_signals(tmp_path: Path) -> None:
    repo = init_fixture_repo(tmp_path)
    write_lines(repo, "src/api/auth_controller.py", 120)
    write_lines(repo, "migrations/001_create_users.sql", 65)

    payload = scope_payload(repo)

    assert payload["scope_backend"] is True
    assert payload["scope_api"] is True
    assert payload["scope_auth"] is True
    assert payload["scope_migrations"] is True
    assert "python" in payload["stack"]
    assert "sql" in payload["stack"]
    assert set(payload["suggested_specialists"]) >= {
        "api-contract",
        "data-migration",
        "maintainability",
        "performance",
        "security",
        "testing",
    }


def test_scope_frontend_and_test_framework_signals(tmp_path: Path) -> None:
    repo = init_fixture_repo(tmp_path)
    write_lines(repo, "web/components/UserCard.test.tsx", 60)

    payload = scope_payload(repo)

    assert payload["scope_frontend"] is True
    assert "javascript" in payload["stack"]
    assert payload["test_framework"] == ["jest/vitest"]
    assert set(payload["suggested_specialists"]) >= {"maintainability", "performance", "testing"}


def test_scope_test_only_python_diff_detects_pytest(tmp_path: Path) -> None:
    repo = init_fixture_repo(tmp_path)
    write_lines(repo, "tests/test_service.py", 60)

    payload = scope_payload(repo)

    assert payload["test_framework"] == ["pytest"]
    assert "testing" in payload["suggested_specialists"]


def test_scope_force_flags_override_small_diff_skip(tmp_path: Path) -> None:
    repo = init_fixture_repo(tmp_path)
    write_lines(repo, "README.md", 2)

    payload = scope_payload(repo, "--security")

    assert payload["small_diff_skip"] is False
    assert payload["forced_specialists"] == ["security"]
    assert payload["suggested_specialists"] == ["security"]


def test_merge_findings_normalizes_aliases_and_emits_summary(tmp_path: Path) -> None:
    summary = tmp_path / "summary.md"
    fixture = skill_root() / "tests" / "fixtures" / "merge_findings_valid.jsonl"

    result = run_script("merge-findings", str(fixture), "--summary-out", str(summary))

    assert result.returncode == 0, result.stderr
    payload = json.loads(result.stdout)
    assert payload["schema"] == "code-review-specialists.merge.v1"
    assert payload["finding_counts"] == {"displayed": 2, "suppressed": 0, "total": 2}
    assert [item["severity"] for item in payload["findings"]] == ["critical", "info"]
    assert payload["red_team_required"] is True
    assert "Specialist Findings Summary" in payload["summary_markdown"]
    assert summary.read_text(encoding="utf-8") == payload["summary_markdown"]


def test_merge_findings_rejects_malformed_jsonl_with_line_number() -> None:
    fixture = skill_root() / "tests" / "fixtures" / "merge_findings_malformed.jsonl"

    result = run_script("merge-findings", str(fixture))

    assert result.returncode == 1
    assert "merge_findings_malformed.jsonl:2" in result.stderr
    assert "malformed JSON" in result.stderr


def test_merge_findings_deduplicates_by_fingerprint_and_keeps_highest_confidence() -> None:
    fixture = skill_root() / "tests" / "fixtures" / "merge_findings_duplicates.jsonl"

    result = run_script("merge-findings", str(fixture))

    assert result.returncode == 0, result.stderr
    payload = json.loads(result.stdout)
    assert payload["finding_counts"]["total"] == 1
    finding = payload["findings"][0]
    assert finding["confidence"] == 0.86
    assert finding["severity"] == "high"
    assert finding["confirming_specialists"] == ["maintainability", "testing"]


def test_merge_findings_suppresses_low_confidence_from_main_summary() -> None:
    fixture = skill_root() / "tests" / "fixtures" / "merge_findings_low_confidence.jsonl"

    result = run_script("merge-findings", str(fixture))

    assert result.returncode == 0, result.stderr
    payload = json.loads(result.stdout)
    assert payload["finding_counts"] == {"displayed": 0, "suppressed": 1, "total": 1}
    assert payload["findings"] == []
    assert "Cache churn may increase" not in payload["summary_markdown"].split("## Findings", 1)[1].split(
        "## Low-Confidence Appendix", 1
    )[0]
    assert "Cache churn may increase" in payload["summary_markdown"].split("## Low-Confidence Appendix", 1)[1]


def test_merge_findings_accepts_red_team_findings() -> None:
    fixture = skill_root() / "tests" / "fixtures" / "merge_findings_red_team.jsonl"

    result = run_script("merge-findings", str(fixture))

    assert result.returncode == 0, result.stderr
    payload = json.loads(result.stdout)
    assert payload["findings"][0]["specialist"] == "red-team"
    assert "red-team" in payload["specialist_stats"]


def test_helper_does_not_emit_review_decision() -> None:
    fixture = skill_root() / "tests" / "fixtures" / "merge_findings_valid.jsonl"

    result = run_script("merge-findings", str(fixture))

    assert result.returncode == 0, result.stderr
    payload = json.loads(result.stdout)
    assert "decision" not in payload
    assert "merge_decision" not in payload
