from __future__ import annotations

import json
import shutil
import subprocess
import sys
from pathlib import Path

import pytest

from aictx import cli
from aictx.middleware import finalize_execution, prepare_execution
from aictx.runtime_launcher import run_execution
from aictx.scaffold import init_repo_scaffold
from aictx.state import write_json
from aictx.work_state import load_work_state, start_work_state

GIT_AVAILABLE = shutil.which("git") is not None


def _payload(repo: Path, execution_id: str = "exec-work-state") -> dict:
    return {
        "repo_root": str(repo),
        "user_request": "continue work state task",
        "agent_id": "codex",
        "adapter_id": "codex",
        "execution_id": execution_id,
        "timestamp": "2026-04-27T10:00:00Z",
    }


def _git(repo: Path, *args: str) -> str:
    result = subprocess.run(["git", *args], cwd=repo, check=True, capture_output=True, text=True)
    return result.stdout.strip()


def _init_git_repo(repo: Path) -> None:
    _git(repo, "init", "-b", "main")
    _git(repo, "config", "user.name", "Test User")
    _git(repo, "config", "user.email", "test@example.com")
    tracked = repo / "tracked.txt"
    tracked.write_text("base\n", encoding="utf-8")
    _git(repo, "add", ".")
    _git(repo, "commit", "-m", "initial")


def test_prepare_execution_exposes_active_work_state(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    start_work_state(repo, "Fix login token refresh", initial={"next_action": "inspect interceptor"})

    prepared = prepare_execution(_payload(repo))

    assert prepared["active_work_state"]["task_id"] == "fix-login-token-refresh"
    assert prepared["active_work_state"]["next_action"] == "inspect interceptor"
    assert prepared["continuity_context"]["active_work_state"]["goal"] == "Fix login token refresh"
    assert prepared["continuity_context"]["loaded"]["work_state"] is True


def test_prepare_execution_without_active_work_state_is_empty(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)

    prepared = prepare_execution(_payload(repo))

    assert prepared["active_work_state"] == {}
    assert "work_state" not in prepared["continuity_context"]["loaded"]


@pytest.mark.skipif(not GIT_AVAILABLE, reason="git binary unavailable")
def test_prepare_execution_exposes_skipped_work_state_when_branch_mismatch_is_unsafe(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    _init_git_repo(repo)
    _git(repo, "checkout", "-b", "feature/login")
    (repo / "tracked.txt").write_text("base\nfeature-only\n", encoding="utf-8")
    _git(repo, "add", "tracked.txt")
    _git(repo, "commit", "-m", "feature commit")
    start_work_state(repo, "Fix login token refresh", initial={"next_action": "inspect interceptor"})
    _git(repo, "checkout", "main")
    (repo / "tracked.txt").write_text("base\nmain-only\n", encoding="utf-8")
    _git(repo, "add", "tracked.txt")
    _git(repo, "commit", "-m", "main change")

    prepared = prepare_execution(_payload(repo, "exec-work-state-skipped"))

    assert prepared["active_work_state"] == {}
    assert prepared["work_state_git_status"]["reason"] == "branch_mismatch_unmerged"
    assert prepared["skipped_work_state"]["task_id"] == "fix-login-token-refresh"
    assert prepared["continuity_context"]["loaded"]["work_state"] is False
    assert prepared["continuity_context"]["skipped_work_state"]["reason"] == "branch_mismatch_unmerged"


@pytest.mark.skipif(not GIT_AVAILABLE, reason="git binary unavailable")
def test_prepare_execution_exposes_active_work_state_when_branch_mismatch_is_safely_merged(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    _init_git_repo(repo)
    _git(repo, "checkout", "-b", "feature/login")
    (repo / "tracked.txt").write_text("base\nfeature\n", encoding="utf-8")
    _git(repo, "add", "tracked.txt")
    _git(repo, "commit", "-m", "feature commit")
    start_work_state(repo, "Fix login token refresh", initial={"next_action": "inspect interceptor"})
    _git(repo, "checkout", "main")
    _git(repo, "merge", "--no-ff", "feature/login", "-m", "merge feature")

    prepared = prepare_execution(_payload(repo, "exec-work-state-merged"))

    assert prepared["active_work_state"]["task_id"] == "fix-login-token-refresh"
    assert prepared["work_state_git_status"]["reason"] == "branch_changed_but_merged"
    assert "skipped_work_state" not in prepared
    assert prepared["continuity_context"]["loaded"]["work_state"] is True


def test_finalize_execution_updates_active_work_state_conservatively(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    start_work_state(repo, "Fix login token refresh")
    prepared = prepare_execution({**_payload(repo), "files_opened": ["src/api/client.ts"], "commands_executed": ["pytest -q tests/test_auth.py"]})

    finalized = finalize_execution(prepared, {"success": True, "result_summary": "tests passed"})
    state = load_work_state(repo, "fix-login-token-refresh")

    assert finalized["work_state_updated"]["updated"] is True
    assert finalized["work_state_updated"]["task_id"] == "fix-login-token-refresh"
    assert "src/api/client.ts" in state["active_files"]
    assert "Command succeeded: pytest -q tests/test_auth.py" in state["verified"]
    assert "pytest -q tests/test_auth.py" in state["recommended_commands"]


def test_finalize_execution_with_failure_does_not_invent_verified(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    start_work_state(repo, "Fix login token refresh")
    prepared = prepare_execution({**_payload(repo), "commands_executed": ["pytest -q"], "notable_errors": ["AssertionError: token stale"]})

    finalize_execution(prepared, {"success": False, "result_summary": "test failed"})
    state = load_work_state(repo, "fix-login-token-refresh")

    assert state["verified"] == []
    assert "Observed error: AssertionError: token stale" in state["risks"]


def test_finalize_execution_carries_contract_gap_into_work_state(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    (repo / "tests").mkdir()
    (repo / "tests/test_parser.py").write_text("def test_parser():\n    pass\n", encoding="utf-8")
    prepared = prepare_execution({
        **_payload(repo, "exec-contract-gap"),
        "user_request": "fix parser",
        "files_opened": ["tests/test_parser.py"],
    })
    prepared["resume_contract"] = {
        "execution_contract": {
            "task_goal": "fix parser",
            "first_action": {"path": "tests/test_parser.py", "binding": "must_open_first"},
            "edit_scope": {"primary": ["tests/test_parser.py"], "secondary_if_needed": []},
            "test_command": {"command": "make test"},
        },
        "task_goal": "fix parser",
    }

    finalized = finalize_execution(prepared, {"success": True, "result_summary": "changed parser but did not run canonical test"})
    state = load_work_state(repo, "contract-carryover-fix-parser")

    assert finalized["contract_gaps"][0]["kind"] == "missing_validation"
    assert finalized["contract_gaps"][0]["severity"] == "needs-validation"
    assert finalized["contract_gaps"][0]["blocking"] is False
    assert finalized["work_state_updated"]["task_id"] == "contract-carryover-fix-parser"
    assert state["status"] == "paused"
    assert any("needs-validation" in item and "Canonical test command was not observed." in item for item in state["unverified"])
    assert state["contract_gaps"][0]["severity"] == "needs-validation"
    assert state["strongest_contract_gap"]["severity"] == "needs-validation"
    assert "make test" in state["recommended_commands"]
    assert state["next_action"] == "run expected validation command: make test"
    assert "exec-contract-gap" in state["source_execution_ids"]


def test_finalize_execution_accepts_explicit_work_state_patch(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    start_work_state(repo, "Fix login token refresh")
    prepared = prepare_execution(_payload(repo))

    finalized = finalize_execution(
        prepared,
        {
            "success": True,
            "result_summary": "preserved next action",
            "work_state": {"current_hypothesis": "token not persisted", "next_action": "run auth tests"},
        },
    )
    state = load_work_state(repo, "fix-login-token-refresh")

    assert finalized["work_state_updated"]["updated"] is True
    assert state["current_hypothesis"] == "token not persisted"
    assert state["next_action"] == "run auth tests"


@pytest.mark.skipif(not GIT_AVAILABLE, reason="git binary unavailable")
def test_finalize_execution_does_not_update_skipped_branch_unsafe_work_state(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    _init_git_repo(repo)
    _git(repo, "checkout", "-b", "feature/login")
    (repo / "tracked.txt").write_text("base\nfeature-only\n", encoding="utf-8")
    _git(repo, "add", "tracked.txt")
    _git(repo, "commit", "-m", "feature commit")
    start_work_state(repo, "Fix login token refresh", initial={"next_action": "inspect interceptor"})
    _git(repo, "checkout", "main")
    (repo / "tracked.txt").write_text("base\nmain-only\n", encoding="utf-8")
    _git(repo, "add", "tracked.txt")
    _git(repo, "commit", "-m", "main change")

    before = load_work_state(repo, "fix-login-token-refresh")
    prepared = prepare_execution({
        **_payload(repo, "exec-work-state-finalize-skipped"),
        "files_opened": ["src/api/client.ts"],
        "commands_executed": ["pytest -q tests/test_auth.py"],
    })

    finalized = finalize_execution(prepared, {"success": True, "result_summary": "should not update skipped work state"})
    after = load_work_state(repo, "fix-login-token-refresh")

    assert prepared["skipped_work_state"]["task_id"] == "fix-login-token-refresh"
    assert finalized["work_state_updated"] is None
    assert after == before


def test_run_execution_propagates_work_state_json_and_records_success(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    start_work_state(repo, "Validate work state continuity")

    outcome = run_execution(
        {**_payload(repo, "exec-run-work-state"), "work_state": {"next_action": "review runtime update"}},
        [sys.executable, "-c", "print('ok')"],
    )
    state = load_work_state(repo, "validate-work-state-continuity")

    assert outcome["exit_code"] == 0
    assert outcome["finalized"]["work_state_updated"]["updated"] is True
    assert state["next_action"] == "review runtime update"
    assert any(item.startswith("Command succeeded:") for item in state["verified"])


def test_internal_cli_accepts_work_state_json_args() -> None:
    parser = cli.build_parser()
    prepare = parser.parse_args([
        "internal", "execution", "prepare", "--task", "x", "--agent-id", "codex", "--execution-id", "exec-1", "--work-state-json", '{"next_action":"x"}', "--work-state-file", "work-state.json",
    ])
    finalize = parser.parse_args([
        "internal", "execution", "finalize", "--prepared", "prepared.json", "--work-state-json", '{"next_action":"x"}', "--work-state-file", "work-state.json",
    ])
    run = parser.parse_args([
        "internal", "run-execution", "--task", "x", "--agent-id", "codex", "--work-state-json", '{"next_action":"x"}', "--work-state-file", "work-state.json", "--", sys.executable, "-c", "pass",
    ])

    assert prepare.work_state_json == '{"next_action":"x"}'
    assert prepare.work_state_file == "work-state.json"
    assert finalize.work_state_json == '{"next_action":"x"}'
    assert finalize.work_state_file == "work-state.json"
    assert run.work_state_json == '{"next_action":"x"}'
    assert run.work_state_file == "work-state.json"


def test_internal_finalize_accepts_work_state_file(tmp_path: Path, capsys) -> None:
    repo = tmp_path / "repo"
    init_repo_scaffold(repo, update_gitignore=False)
    start_work_state(repo, "Fix login token refresh")
    prepared = prepare_execution(_payload(repo, "exec-work-state-file"))
    prepared_path = tmp_path / "prepared.json"
    patch_path = tmp_path / "work-state.json"
    write_json(prepared_path, prepared)
    write_json(patch_path, {"unverified": ["manual auth flow"], "next_action": "run browser auth smoke"})

    parser = cli.build_parser()
    args = parser.parse_args([
        "internal", "execution", "finalize",
        "--prepared", str(prepared_path),
        "--success",
        "--result-summary", "preserved explicit work state file",
        "--work-state-file", str(patch_path),
    ])

    assert args.func(args) == 0
    finalized = json.loads(capsys.readouterr().out)
    state = load_work_state(repo, "fix-login-token-refresh")
    assert finalized["work_state_updated"]["updated"] is True
    assert state["unverified"] == ["manual auth flow"]
    assert state["next_action"] == "run browser auth smoke"
