"""Steps for plan_diff_worktree.feature."""

from __future__ import annotations

import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

from behave import given, then, when
from behave.runner import Context


def _git(args: list[str], cwd: str) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        ["git", *args],
        cwd=cwd,
        capture_output=True,
        text=True,
        check=True,
        timeout=10,
    )


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("the plan-diff in-memory database is initialized")
def step_pdt_init(context: Context) -> None:
    context.pdt_diff_output: str | None = None  # type: ignore[attr-defined]


# ---------------------------------------------------------------------------
# Given — repo fixtures
# ---------------------------------------------------------------------------


@given('a temp git repo with a worktree branch for plan "{plan_id}" for pdt')
def step_create_repo_with_branch(context: Context, plan_id: str) -> None:
    d = tempfile.mkdtemp(prefix="pdt-")
    context.add_cleanup(shutil.rmtree, d, True)  # type: ignore[attr-defined]
    _git(["init", "-q", "-b", "main"], d)
    _git(["config", "user.name", "T"], d)
    _git(["config", "user.email", "t@t"], d)
    _git(["config", "commit.gpgsign", "false"], d)
    Path(d, "README.md").write_text("initial\n")
    _git(["add", "."], d)
    _git(["commit", "-q", "-m", "init"], d)

    branch = f"cleveragents/plan-{plan_id}"
    wt_dir = tempfile.mkdtemp(prefix="pdt-wt-")
    context.add_cleanup(shutil.rmtree, wt_dir, True)  # type: ignore[attr-defined]
    _git(["worktree", "add", "-b", branch, wt_dir, "HEAD"], d)

    context.pdt_repo = d  # type: ignore[attr-defined]
    context.pdt_wt_dir = wt_dir  # type: ignore[attr-defined]
    context.pdt_plan_id = plan_id  # type: ignore[attr-defined]
    context.pdt_branch = branch  # type: ignore[attr-defined]


@given('a file "{filename}" is changed on the worktree branch for pdt')
def step_change_file_on_branch(context: Context, filename: str) -> None:
    wt_dir: str = context.pdt_wt_dir  # type: ignore[attr-defined]
    Path(wt_dir, filename).write_text("new content\n")
    _git(["add", "."], wt_dir)
    _git(["commit", "-q", "-m", f"add {filename}"], wt_dir)


@given("a temp git repo without a worktree branch for pdt")
def step_create_clean_repo(context: Context) -> None:
    d = tempfile.mkdtemp(prefix="pdt-clean-")
    context.add_cleanup(shutil.rmtree, d, True)  # type: ignore[attr-defined]
    _git(["init", "-q", "-b", "main"], d)
    _git(["config", "user.name", "T"], d)
    _git(["config", "user.email", "t@t"], d)
    _git(["config", "commit.gpgsign", "false"], d)
    Path(d, "README.md").write_text("initial\n")
    _git(["add", "."], d)
    _git(["commit", "-q", "-m", "init"], d)
    context.pdt_repo = d  # type: ignore[attr-defined]


@given("a mocked service that resolves the git resource for pdt")
def step_mock_service_with_resource(context: Context) -> None:
    mock_resource = MagicMock()
    mock_resource.resource_type_name = "git-checkout"
    mock_resource.location = context.pdt_repo  # type: ignore[attr-defined]
    mock_resource.resource_id = "res-pdt-test"

    mock_lr = MagicMock()
    mock_lr.resource_id = "res-pdt-test"

    mock_project = MagicMock()
    mock_project.linked_resources = [mock_lr]

    mock_plan = MagicMock()
    mock_plan.project_links = [MagicMock(project_name="local/pdt-test")]

    mock_service = MagicMock()
    mock_service.get_plan.return_value = mock_plan

    mock_project_repo = MagicMock()
    mock_project_repo.get.return_value = mock_project

    mock_resource_registry = MagicMock()
    mock_resource_registry.show_resource.return_value = mock_resource

    mock_container = MagicMock()
    mock_container.namespaced_project_repo.return_value = mock_project_repo
    mock_container.resource_registry_service.return_value = mock_resource_registry

    context.pdt_service = mock_service  # type: ignore[attr-defined]
    context.pdt_container = mock_container  # type: ignore[attr-defined]


@given("a mocked service with no linked resources for plan diff for pdt")
def step_mock_service_no_resources(context: Context) -> None:
    mock_plan = MagicMock()
    mock_plan.project_links = []

    mock_service = MagicMock()
    mock_service.get_plan.return_value = mock_plan

    mock_container = MagicMock()

    context.pdt_service = mock_service  # type: ignore[attr-defined]
    context.pdt_container = mock_container  # type: ignore[attr-defined]


# ---------------------------------------------------------------------------
# When — Infrastructure layer
# ---------------------------------------------------------------------------


@when('I call diff_against_head for plan "{plan_id}" for pdt')
def step_call_diff_against_head(context: Context, plan_id: str) -> None:
    from cleveragents.infrastructure.sandbox.git_worktree import (
        GitWorktreeSandbox,
    )

    context.pdt_diff_output = GitWorktreeSandbox.diff_against_head(  # type: ignore[attr-defined]
        context.pdt_repo,  # type: ignore[attr-defined]
        plan_id,
    )


# ---------------------------------------------------------------------------
# When — CLI layer
# ---------------------------------------------------------------------------


@when('I call _get_worktree_diff for plan "{plan_id}" for pdt')
def step_call_get_worktree_diff(context: Context, plan_id: str) -> None:
    from cleveragents.cli.commands.plan import _get_worktree_diff

    service: Any = context.pdt_service  # type: ignore[attr-defined]
    container: Any = context.pdt_container  # type: ignore[attr-defined]

    with patch(
        "cleveragents.cli.commands.plan.get_container",
        return_value=container,
    ):
        context.pdt_diff_output = _get_worktree_diff(plan_id, service)  # type: ignore[attr-defined]


# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------


@then('the diff output should contain "{text}" for pdt')
def step_diff_contains(context: Context, text: str) -> None:
    output: str | None = context.pdt_diff_output  # type: ignore[attr-defined]
    assert output is not None, "Expected diff output but got None"
    assert text in output, f"Expected '{text}' in diff output, got: {output[:200]}"


@then("the diff output should not be None for pdt")
def step_diff_not_none(context: Context) -> None:
    assert context.pdt_diff_output is not None, "Expected diff output but got None"  # type: ignore[attr-defined]


@then("the diff output should be None for pdt")
def step_diff_is_none(context: Context) -> None:
    assert context.pdt_diff_output is None, (  # type: ignore[attr-defined]
        f"Expected None but got: {context.pdt_diff_output}"  # type: ignore[attr-defined]
    )
