"""Step definitions for non-sandboxed resource coverage scenarios.

Covers uncovered lines in ``no_sandbox.py``:
- Empty resource_id / original_path validation (lines 51, 53)
- Context property before create (line 76)
- Empty plan_id validation (line 94)
- get_path state guard (lines 136-137)
- Path traversal guard (line 142)
- CREATED -> ACTIVE transition on first path resolution (line 146-148)
- commit state guard (line 169)
- Idempotent cleanup (line 212)
"""

from __future__ import annotations

import os

from behave import given, then, when

from cleveragents.infrastructure.sandbox.no_sandbox import NoSandbox
from cleveragents.infrastructure.sandbox.protocol import (
    SandboxRollbackError,
    SandboxStateError,
)

# ---------------------------------------------------------------------------
# Givens
# ---------------------------------------------------------------------------


@given('a non-sandboxed resource for "{resource_id}" at "{path}"')
def given_nosandbox_instance(context, resource_id: str, path: str):
    """Create a NoSandbox instance with valid identifiers."""
    context.nosandbox = NoSandbox(resource_id=resource_id, original_path=path)
    context.nosandbox_resource_id = resource_id
    context.nosandbox_path = path


@given('another non-sandboxed resource for "{resource_id}" at "{path}"')
def given_another_nosandbox_instance(context, resource_id: str, path: str):
    """Create a second NoSandbox instance for identity comparison."""
    context.nosandbox_other = NoSandbox(resource_id=resource_id, original_path=path)


@given('the non-sandboxed resource has been created for plan "{plan_id}"')
def given_nosandbox_created(context, plan_id: str):
    """Move the NoSandbox through the create step."""
    context.nosandbox.create(plan_id)


@given('the file "{filename}" has been resolved against the non-sandboxed resource')
def given_nosandbox_file_resolved(context, filename: str):
    """Resolve a file path so the sandbox transitions to ACTIVE."""
    context.nosandbox.get_path(filename)


@given("the non-sandboxed resource has been created and committed and cleaned up")
def given_nosandbox_cleaned_up(context):
    """Drive the NoSandbox through a full lifecycle ending in CLEANED_UP."""
    context.nosandbox.create("01JKXYZ1234567890ABCDEFGH")
    context.nosandbox.commit()
    context.nosandbox.cleanup()


@given("the non-sandboxed resource has been committed")
def given_nosandbox_committed(context):
    """Commit the NoSandbox (assumes it was already created)."""
    context.nosandbox.commit()


# ---------------------------------------------------------------------------
# Whens
# ---------------------------------------------------------------------------


@when("a non-sandboxed resource is prepared with an empty identifier")
def when_nosandbox_empty_resource_id(context):
    """Attempt to create a NoSandbox with an empty resource_id."""
    context.nosandbox_error = None
    try:
        NoSandbox(resource_id="", original_path="/some/path")
    except ValueError as exc:
        context.nosandbox_error = exc


@when("a non-sandboxed resource is prepared with an empty path")
def when_nosandbox_empty_path(context):
    """Attempt to create a NoSandbox with an empty original_path."""
    context.nosandbox_error = None
    try:
        NoSandbox(resource_id="valid-id", original_path="")
    except ValueError as exc:
        context.nosandbox_error = exc


@when("the non-sandboxed resource is created with an empty plan identifier")
def when_nosandbox_create_empty_plan(context):
    """Attempt to create a sandbox with an empty plan_id."""
    context.nosandbox_error = None
    try:
        context.nosandbox.create("")
    except ValueError as exc:
        context.nosandbox_error = exc


@when('the non-sandboxed resource is created for plan "{plan_id}"')
def when_nosandbox_create(context, plan_id: str):
    """Create the NoSandbox with the given plan_id."""
    context.nosandbox_context = context.nosandbox.create(plan_id)


@when("a file path is resolved against the non-sandboxed resource")
def when_nosandbox_resolve_path(context):
    """Attempt to resolve a path (may fail if state is wrong)."""
    context.nosandbox_error = None
    try:
        context.nosandbox_resolved = context.nosandbox.get_path("any/file.txt")
    except (SandboxStateError, ValueError) as exc:
        context.nosandbox_error = exc


@when("a file path containing directory traversal is resolved")
def when_nosandbox_resolve_traversal(context):
    """Attempt to resolve a path containing '..'."""
    context.nosandbox_error = None
    try:
        context.nosandbox_resolved = context.nosandbox.get_path("../etc/passwd")
    except ValueError as exc:
        context.nosandbox_error = exc


@when('the file "{filename}" is resolved against the non-sandboxed resource')
def when_nosandbox_resolve_specific_file(context, filename: str):
    """Resolve a specific file path against the NoSandbox."""
    context.nosandbox_resolved = context.nosandbox.get_path(filename)


@when("the non-sandboxed resource is committed")
def when_nosandbox_commit(context):
    """Attempt to commit the NoSandbox."""
    context.nosandbox_error = None
    try:
        context.nosandbox_commit_result = context.nosandbox.commit()
    except SandboxStateError as exc:
        context.nosandbox_error = exc


@when("the non-sandboxed resource is rolled back")
def when_nosandbox_rollback(context):
    """Attempt to rollback the NoSandbox."""
    context.nosandbox_error = None
    try:
        context.nosandbox.rollback()
    except SandboxRollbackError as exc:
        context.nosandbox_error = exc


@when("the non-sandboxed resource is cleaned up again")
def when_nosandbox_cleanup_again(context):
    """Call cleanup on an already cleaned-up NoSandbox."""
    context.nosandbox.cleanup()


@when("the non-sandboxed resource is cleaned up")
def when_nosandbox_cleanup(context):
    """Clean up the NoSandbox."""
    context.nosandbox.cleanup()


# ---------------------------------------------------------------------------
# Thens
# ---------------------------------------------------------------------------


@then("the non-sandboxed setup should fail with a resource identifier error")
def then_nosandbox_error_resource_id(context):
    """Verify a ValueError was raised about resource_id."""
    assert context.nosandbox_error is not None, "Expected a ValueError"
    assert isinstance(context.nosandbox_error, ValueError)
    assert "resource_id" in str(context.nosandbox_error)


@then("the non-sandboxed setup should fail with a path error")
def then_nosandbox_error_path(context):
    """Verify a ValueError was raised about original_path."""
    assert context.nosandbox_error is not None, "Expected a ValueError"
    assert isinstance(context.nosandbox_error, ValueError)
    assert "original_path" in str(context.nosandbox_error)


@then("the non-sandboxed resource should have no context")
def then_nosandbox_no_context(context):
    """Verify context is None before create() is called."""
    assert context.nosandbox.context is None


@then("the non-sandboxed setup should fail with a plan identifier error")
def then_nosandbox_error_plan_id(context):
    """Verify a ValueError was raised about plan_id."""
    assert context.nosandbox_error is not None, "Expected a ValueError"
    assert isinstance(context.nosandbox_error, ValueError)
    assert "plan_id" in str(context.nosandbox_error)


@then('the non-sandboxed context should reference plan "{plan_id}"')
def then_nosandbox_context_plan(context, plan_id: str):
    """Verify the context references the expected plan."""
    assert context.nosandbox_context.plan_id == plan_id


@then('the non-sandboxed context should reference resource "{resource_id}"')
def then_nosandbox_context_resource(context, resource_id: str):
    """Verify the context references the expected resource."""
    assert context.nosandbox_context.resource_id == resource_id


@then("the non-sandboxed context should use the original path as the sandbox path")
def then_nosandbox_context_path_matches(context):
    """Verify sandbox_path == original_path (no isolation)."""
    ctx = context.nosandbox_context
    assert ctx.sandbox_path == ctx.original_path


@then('the non-sandboxed context should have strategy metadata "{strategy}"')
def then_nosandbox_context_metadata(context, strategy: str):
    """Verify the metadata includes the expected strategy."""
    assert context.nosandbox_context.metadata.get("strategy") == strategy


@then("the non-sandboxed path resolution should be rejected as premature")
def then_nosandbox_path_state_error(context):
    """Verify a SandboxStateError was raised for path resolution."""
    assert context.nosandbox_error is not None, "Expected a SandboxStateError"
    assert isinstance(context.nosandbox_error, SandboxStateError)


@then("the non-sandboxed path resolution should be rejected as unsafe")
def then_nosandbox_path_traversal_error(context):
    """Verify a ValueError was raised for directory traversal."""
    assert context.nosandbox_error is not None, "Expected a ValueError"
    assert isinstance(context.nosandbox_error, ValueError)
    assert "traversal" in str(context.nosandbox_error).lower()


@then('the non-sandboxed resource should be in the "{status}" state')
def then_nosandbox_status(context, status: str):
    """Verify the NoSandbox is in the expected status."""
    assert context.nosandbox.status.value == status


@then("the resolved non-sandboxed path should combine the original path and file")
def then_nosandbox_resolved_path(context):
    """Verify the resolved path is os.path.join(original_path, file)."""
    expected = os.path.join(context.nosandbox_path, "src/main.py")
    assert context.nosandbox_resolved == expected


@then("the non-sandboxed commit should be rejected as premature")
def then_nosandbox_commit_state_error(context):
    """Verify a SandboxStateError was raised for commit."""
    assert context.nosandbox_error is not None, "Expected a SandboxStateError"
    assert isinstance(context.nosandbox_error, SandboxStateError)


@then("the non-sandboxed commit result should indicate success")
def then_nosandbox_commit_success(context):
    """Verify the commit returned a successful result."""
    assert context.nosandbox_commit_result.success is True


@then("the non-sandboxed rollback should be rejected as impossible")
def then_nosandbox_rollback_error(context):
    """Verify a SandboxRollbackError was raised."""
    assert context.nosandbox_error is not None, "Expected a SandboxRollbackError"
    assert isinstance(context.nosandbox_error, SandboxRollbackError)
    assert "not possible" in str(context.nosandbox_error).lower()


@then('the non-sandboxed resource should remain in the "{status}" state')
def then_nosandbox_status_remains(context, status: str):
    """Verify the NoSandbox stayed in the expected status after idempotent op."""
    assert context.nosandbox.status.value == status


@then("the two non-sandboxed resources should have different identifiers")
def then_nosandbox_unique_ids(context):
    """Verify two NoSandbox instances have distinct sandbox_ids."""
    assert context.nosandbox.sandbox_id != context.nosandbox_other.sandbox_id
