"""Step definitions for devcontainer_autodiscovery_wiring.feature.

Tests that discover_devcontainers() is correctly wired into
GitCheckoutHandler.discover_children() and FsDirectoryHandler.discover_children().

All steps use the ``dcwire`` prefix to avoid Behave AmbiguousStep errors.
"""

from __future__ import annotations

import json
import subprocess
import tempfile
from pathlib import Path

from behave import given, then, when

from cleveragents.domain.models.core.resource import PhysVirt, Resource
from cleveragents.resource.handlers.fs_directory import FsDirectoryHandler
from cleveragents.resource.handlers.git_checkout import GitCheckoutHandler

_VALID_ULID = "01JQDVHN5X5QJKBMZ3AP8Y4G7K"
_DEVCONTAINER_JSON = json.dumps({"name": "Test Dev Container", "image": "ubuntu:22.04"})


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_git_resource(location: str) -> Resource:
    """Create a minimal git-checkout Resource."""
    return Resource(
        resource_id=_VALID_ULID,
        name="test-repo",
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        description="Test git checkout resource",
        location=location,
    )


def _make_fs_resource(location: str) -> Resource:
    """Create a minimal fs-directory Resource."""
    return Resource(
        resource_id=_VALID_ULID,
        name="test-dir",
        resource_type_name="fs-directory",
        classification=PhysVirt.PHYSICAL,
        description="Test filesystem directory resource",
        location=location,
    )


def _init_git_repo(tmpdir: str, extra_files: dict[str, str] | None = None) -> str:
    """Initialise a bare-minimum git repo with an initial commit."""
    subprocess.run(["git", "init"], cwd=tmpdir, capture_output=True, check=True)
    subprocess.run(
        ["git", "config", "user.email", "test@dcwire.dev"],
        cwd=tmpdir,
        capture_output=True,
        check=True,
    )
    subprocess.run(
        ["git", "config", "user.name", "DCWire Test"],
        cwd=tmpdir,
        capture_output=True,
        check=True,
    )
    subprocess.run(
        ["git", "config", "commit.gpgSign", "false"],
        cwd=tmpdir,
        capture_output=True,
        check=True,
    )
    # Always create a README so there is at least one tracked file
    readme = Path(tmpdir) / "README.md"
    readme.write_text("# dcwire test\n")

    if extra_files:
        for rel_path, file_content in extra_files.items():
            full = Path(tmpdir) / rel_path
            full.parent.mkdir(parents=True, exist_ok=True)
            full.write_text(file_content)

    subprocess.run(["git", "add", "."], cwd=tmpdir, capture_output=True, check=True)
    subprocess.run(
        ["git", "commit", "-m", "init"],
        cwd=tmpdir,
        capture_output=True,
        check=True,
    )
    return tmpdir


def _create_devcontainer_dir(base: str, rel_path: str) -> None:
    """Create a devcontainer.json at the given relative path inside base."""
    full = Path(base) / rel_path
    full.parent.mkdir(parents=True, exist_ok=True)
    full.write_text(_DEVCONTAINER_JSON)


# ---------------------------------------------------------------------------
# GIVEN steps — git-checkout
# ---------------------------------------------------------------------------


@given('dcwire a git repo with a ".devcontainer/devcontainer.json" file')
def step_dcwire_git_with_root_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-git-")
    _create_devcontainer_dir(tmpdir, ".devcontainer/devcontainer.json")
    _init_git_repo(tmpdir, {".devcontainer/devcontainer.json": _DEVCONTAINER_JSON})
    ctx.dcwire_handler = GitCheckoutHandler()
    ctx.dcwire_resource = _make_git_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given('dcwire a git repo with a ".devcontainer/api/devcontainer.json" named config')
def step_dcwire_git_with_named_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-git-named-")
    _create_devcontainer_dir(tmpdir, ".devcontainer/api/devcontainer.json")
    _init_git_repo(tmpdir, {".devcontainer/api/devcontainer.json": _DEVCONTAINER_JSON})
    ctx.dcwire_handler = GitCheckoutHandler()
    ctx.dcwire_resource = _make_git_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given("dcwire a git repo with no devcontainer configuration")
def step_dcwire_git_no_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-git-nodc-")
    _init_git_repo(tmpdir)
    ctx.dcwire_handler = GitCheckoutHandler()
    ctx.dcwire_resource = _make_git_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given(
    'dcwire a git repo with a subdirectory "src" and a ".devcontainer/devcontainer.json" file'
)
def step_dcwire_git_with_src_and_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-git-src-")
    _create_devcontainer_dir(tmpdir, ".devcontainer/devcontainer.json")
    _init_git_repo(
        tmpdir,
        {
            "src/main.py": "print('hello')\n",
            ".devcontainer/devcontainer.json": _DEVCONTAINER_JSON,
        },
    )
    ctx.dcwire_handler = GitCheckoutHandler()
    ctx.dcwire_resource = _make_git_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given('dcwire a git repo with a root-level ".devcontainer.json" file')
def step_dcwire_git_with_root_level_devcontainer_json(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-git-rootdc-")
    _create_devcontainer_dir(tmpdir, ".devcontainer.json")
    _init_git_repo(tmpdir, {".devcontainer.json": _DEVCONTAINER_JSON})
    ctx.dcwire_handler = GitCheckoutHandler()
    ctx.dcwire_resource = _make_git_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


# ---------------------------------------------------------------------------
# GIVEN steps — fs-directory
# ---------------------------------------------------------------------------


@given('dcwire a filesystem directory with a ".devcontainer/devcontainer.json" file')
def step_dcwire_fs_with_root_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-fs-")
    _create_devcontainer_dir(tmpdir, ".devcontainer/devcontainer.json")
    ctx.dcwire_handler = FsDirectoryHandler()
    ctx.dcwire_resource = _make_fs_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given(
    'dcwire a filesystem directory with a ".devcontainer/frontend/devcontainer.json" named config'
)
def step_dcwire_fs_with_named_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-fs-named-")
    _create_devcontainer_dir(tmpdir, ".devcontainer/frontend/devcontainer.json")
    ctx.dcwire_handler = FsDirectoryHandler()
    ctx.dcwire_resource = _make_fs_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given("dcwire a filesystem directory with no devcontainer configuration")
def step_dcwire_fs_no_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-fs-nodc-")
    ctx.dcwire_handler = FsDirectoryHandler()
    ctx.dcwire_resource = _make_fs_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given(
    'dcwire a filesystem directory with a subdirectory "lib" and a ".devcontainer/devcontainer.json" file'
)
def step_dcwire_fs_with_lib_and_devcontainer(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-fs-lib-")
    lib_dir = Path(tmpdir) / "lib"
    lib_dir.mkdir()
    _create_devcontainer_dir(tmpdir, ".devcontainer/devcontainer.json")
    ctx.dcwire_handler = FsDirectoryHandler()
    ctx.dcwire_resource = _make_fs_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


@given('dcwire a filesystem directory with a root-level ".devcontainer.json" file')
def step_dcwire_fs_with_root_level_devcontainer_json(ctx):
    tmpdir = tempfile.mkdtemp(prefix="dcwire-fs-rootdc-")
    _create_devcontainer_dir(tmpdir, ".devcontainer.json")
    ctx.dcwire_handler = FsDirectoryHandler()
    ctx.dcwire_resource = _make_fs_resource(tmpdir)
    ctx.dcwire_result = None
    ctx.dcwire_error = None
    ctx.dcwire_last_dc_child = None


# ---------------------------------------------------------------------------
# WHEN steps
# ---------------------------------------------------------------------------


@when("dcwire I call discover_children on the git-checkout resource")
def step_dcwire_discover_git(ctx):
    try:
        ctx.dcwire_result = ctx.dcwire_handler.discover_children(
            resource=ctx.dcwire_resource
        )
    except Exception as exc:
        ctx.dcwire_error = exc


@when("dcwire I call discover_children on the fs-directory resource")
def step_dcwire_discover_fs(ctx):
    try:
        ctx.dcwire_result = ctx.dcwire_handler.discover_children(
            resource=ctx.dcwire_resource
        )
    except Exception as exc:
        ctx.dcwire_error = exc


# ---------------------------------------------------------------------------
# THEN steps
# ---------------------------------------------------------------------------


@then('dcwire the children include a "devcontainer-instance" resource named "{name}"')
def step_dcwire_has_devcontainer_child(ctx, name):
    assert ctx.dcwire_error is None, f"Unexpected error: {ctx.dcwire_error}"
    assert ctx.dcwire_result is not None, "Expected a list of children"
    dc_children = [
        r
        for r in ctx.dcwire_result
        if r.resource_type_name == "devcontainer-instance" and r.name == name
    ]
    assert len(dc_children) == 1, (
        f"Expected exactly one devcontainer-instance named '{name}', "
        f"got {[r.name for r in ctx.dcwire_result if r.resource_type_name == 'devcontainer-instance']}"
    )
    ctx.dcwire_last_dc_child = dc_children[0]


@then('dcwire the children include a "fs-directory" resource named "{name}"')
def step_dcwire_has_fs_child(ctx, name):
    assert ctx.dcwire_error is None, f"Unexpected error: {ctx.dcwire_error}"
    assert ctx.dcwire_result is not None, "Expected a list of children"
    fs_children = [
        r
        for r in ctx.dcwire_result
        if r.resource_type_name == "fs-directory" and r.name == name
    ]
    assert len(fs_children) == 1, (
        f"Expected exactly one fs-directory named '{name}', "
        f"got {[r.name for r in ctx.dcwire_result if r.resource_type_name == 'fs-directory']}"
    )


@then('dcwire the devcontainer child has provisioning_state "{state}"')
def step_dcwire_has_provisioning_state(ctx, state):
    assert ctx.dcwire_last_dc_child is not None, "No devcontainer child captured"
    props = ctx.dcwire_last_dc_child.properties or {}
    actual = props.get("provisioning_state")
    assert actual == state, f"Expected provisioning_state='{state}', got '{actual}'"


@then("dcwire the devcontainer child has devcontainer_json_path set")
def step_dcwire_has_json_path(ctx):
    assert ctx.dcwire_last_dc_child is not None, "No devcontainer child captured"
    props = ctx.dcwire_last_dc_child.properties or {}
    path_val = props.get("devcontainer_json_path")
    assert path_val, f"Expected devcontainer_json_path to be set, got {path_val!r}"
    assert "devcontainer.json" in path_val, (
        f"Expected devcontainer_json_path to contain 'devcontainer.json', got {path_val!r}"
    )


@then('dcwire the devcontainer child has config_name "{config_name}"')
def step_dcwire_has_config_name(ctx, config_name):
    assert ctx.dcwire_last_dc_child is not None, "No devcontainer child captured"
    props = ctx.dcwire_last_dc_child.properties or {}
    actual = props.get("config_name")
    assert actual == config_name, (
        f"Expected config_name='{config_name}', got '{actual}'"
    )


@then("dcwire no devcontainer-instance children are present")
def step_dcwire_no_devcontainer_children(ctx):
    assert ctx.dcwire_error is None, f"Unexpected error: {ctx.dcwire_error}"
    assert ctx.dcwire_result is not None, "Expected a list of children"
    dc_children = [
        r for r in ctx.dcwire_result if r.resource_type_name == "devcontainer-instance"
    ]
    assert len(dc_children) == 0, (
        f"Expected no devcontainer-instance children, got {[r.name for r in dc_children]}"
    )
