"""Step definitions for tui_permissions_screen.feature.

Tests cover:
- DiffDisplayMode, FileChangeType, PermissionDecision enums
- PermissionRequest model (unified/side-by-side/context diffs)
- ToolPermissionRequest model (decisions, is_pending, is_allowed, is_rejected)
- PermissionRequestService (queue, decisions, session-scoped decisions)
- PermissionsScreen widget (load, navigate, cycle mode, allow/reject)
- Rendering helpers (_render_file_list, _render_diff_panel, _render_status_bar)
"""

from __future__ import annotations

from behave import given, then, when

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


def _make_permission_request(path: str, before: str, after: str):
    from cleveragents.tui.permissions.models import FileChangeType, PermissionRequest

    return PermissionRequest(
        path=path,
        change_type=FileChangeType.MODIFIED,
        before_content=before,
        after_content=after,
    )


def _make_tool_request(request_id: str, tool_name: str, n_changes: int = 3):
    from cleveragents.tui.permissions.models import (
        FileChangeType,
        PermissionRequest,
        ToolPermissionRequest,
    )

    changes = [
        PermissionRequest(
            path=f"file{i}.py",
            change_type=FileChangeType.MODIFIED,
            before_content=f"old content {i}\nline2\n",
            after_content=f"new content {i}\nline2\n",
        )
        for i in range(n_changes)
    ]
    return ToolPermissionRequest(
        request_id=request_id,
        tool_name=tool_name,
        resource_name="local/api-service",
        changes=changes,
    )


# ---------------------------------------------------------------------------
# DiffDisplayMode enum
# ---------------------------------------------------------------------------


@when("I inspect the DiffDisplayMode enum")
def step_inspect_diff_display_mode(context):
    from cleveragents.tui.permissions.models import DiffDisplayMode

    context._diff_display_mode = DiffDisplayMode


@then('DiffDisplayMode should have values "unified", "side_by_side", "context"')
def step_diff_display_mode_values(context):
    from cleveragents.tui.permissions.models import DiffDisplayMode

    assert DiffDisplayMode.UNIFIED == "unified"
    assert DiffDisplayMode.SIDE_BY_SIDE == "side_by_side"
    assert DiffDisplayMode.CONTEXT == "context"


# ---------------------------------------------------------------------------
# FileChangeType enum
# ---------------------------------------------------------------------------


@when("I inspect the FileChangeType enum")
def step_inspect_file_change_type(context):
    from cleveragents.tui.permissions.models import FileChangeType

    context._file_change_type = FileChangeType


@then('FileChangeType should have values "M", "A", "D"')
def step_file_change_type_values(context):
    from cleveragents.tui.permissions.models import FileChangeType

    assert FileChangeType.MODIFIED == "M"
    assert FileChangeType.ADDED == "A"
    assert FileChangeType.DELETED == "D"


# ---------------------------------------------------------------------------
# PermissionDecision enum
# ---------------------------------------------------------------------------


@when("I inspect the PermissionDecision enum")
def step_inspect_permission_decision(context):
    from cleveragents.tui.permissions.models import PermissionDecision

    context._permission_decision = PermissionDecision


@then(
    'PermissionDecision should have values "allow_once", "allow_always", "reject_once", "reject_always"'
)
def step_permission_decision_values(context):
    from cleveragents.tui.permissions.models import PermissionDecision

    assert PermissionDecision.ALLOW_ONCE == "allow_once"
    assert PermissionDecision.ALLOW_ALWAYS == "allow_always"
    assert PermissionDecision.REJECT_ONCE == "reject_once"
    assert PermissionDecision.REJECT_ALWAYS == "reject_always"


# ---------------------------------------------------------------------------
# PermissionRequest model
# ---------------------------------------------------------------------------

_BEFORE = "def login(self, request):\n    return self.authenticate(request)\n"
_AFTER = (
    "def login(self, request):\n"
    "    result = self.authenticate(request)\n"
    "    self.log_attempt(request, result)\n"
    "    return result\n"
)


@given('a PermissionRequest for "{path}" with before and after content')
def step_create_permission_request(context, path):
    context._perm_request = _make_permission_request(path, _BEFORE, _AFTER)


@given('a PermissionRequest for "{path}" with identical before and after content')
def step_create_identical_permission_request(context, path):
    same = "def foo():\n    pass\n"
    context._perm_request = _make_permission_request(path, same, same)


@when('I render the diff in "{mode}" mode')
def step_render_diff(context, mode):
    from cleveragents.tui.permissions.models import DiffDisplayMode

    mode_enum = DiffDisplayMode(mode)
    context._rendered_diff = context._perm_request.render_diff(mode_enum)


@then('the rendered diff should contain "{text}"')
def step_rendered_diff_contains(context, text):
    assert text in context._rendered_diff, (
        f"Expected {text!r} in diff:\n{context._rendered_diff}"
    )


@then("the rendered diff should be empty")
def step_rendered_diff_empty(context):
    assert context._rendered_diff == "", (
        f"Expected empty diff, got:\n{context._rendered_diff}"
    )


@when("I call side_by_side_diff on the request")
def step_call_side_by_side_diff(context):
    context._sbs_left, context._sbs_right = context._perm_request.side_by_side_diff()


@then("both sides should have the same number of lines")
def step_sbs_same_length(context):
    assert len(context._sbs_left) == len(context._sbs_right), (
        f"Left: {len(context._sbs_left)}, Right: {len(context._sbs_right)}"
    )


# ---------------------------------------------------------------------------
# ToolPermissionRequest model
# ---------------------------------------------------------------------------


@given("a ToolPermissionRequest with no decision")
def step_create_tool_permission_request(context):
    context._tool_request = _make_tool_request("req-0", "local/file-write")
    context._original_request = context._tool_request


@then("the request should be pending")
def step_request_is_pending(context):
    assert context._tool_request.is_pending


@then("the request should not be allowed")
def step_request_not_allowed(context):
    assert not context._tool_request.is_allowed


@then("the request should not be rejected")
def step_request_not_rejected(context):
    assert not context._tool_request.is_rejected


@when('I apply decision "{decision}" to the request')
def step_apply_decision(context, decision):
    from cleveragents.tui.permissions.models import PermissionDecision

    context._decided_request = context._tool_request.apply_decision(
        PermissionDecision(decision)
    )
    # Update context._tool_request to the decided version for subsequent steps
    context._tool_request = context._decided_request


@then("the request should be allowed")
def step_request_is_allowed(context):
    assert context._tool_request.is_allowed


@then("the request should be rejected")
def step_request_is_rejected(context):
    assert context._tool_request.is_rejected


@then("the request should not be pending")
def step_request_not_pending(context):
    assert not context._tool_request.is_pending


@then("the original request should still be pending")
def step_original_request_still_pending(context):
    assert context._original_request.is_pending


# ---------------------------------------------------------------------------
# PermissionRequestService
# ---------------------------------------------------------------------------


@given("a fresh PermissionRequestService")
def step_create_service(context):
    from cleveragents.tui.permissions.service import PermissionRequestService

    context._service = PermissionRequestService()
    context._clear_result = None
    context._decision_result = None


@when('I enqueue a request with id "{request_id}" for tool "{tool_name}"')
def step_enqueue_request(context, request_id, tool_name):
    req = _make_tool_request(request_id, tool_name)
    context._service.enqueue(req)
    context._last_request_id = request_id


@when('I record decision "{decision}" for request "{request_id}"')
def step_record_decision(context, decision, request_id):
    from cleveragents.tui.permissions.models import PermissionDecision

    context._decision_result = context._service.record_decision(
        request_id, PermissionDecision(decision)
    )


@then("the service should have {count:d} pending request")
def step_service_pending_count_singular(context, count):
    assert len(context._service.pending_requests()) == count, (
        f"Expected {count} pending, got {len(context._service.pending_requests())}"
    )


@then("the service should have {count:d} pending requests")
def step_service_pending_count(context, count):
    assert len(context._service.pending_requests()) == count, (
        f"Expected {count} pending, got {len(context._service.pending_requests())}"
    )


@then("the service should have {count:d} total requests")
def step_service_total_count(context, count):
    assert len(context._service.all_requests()) == count, (
        f"Expected {count} total, got {len(context._service.all_requests())}"
    )


@then('the request "{request_id}" should have decision "{decision}"')
def step_request_has_decision(context, request_id, decision):
    req = context._service.get(request_id)
    assert req is not None, f"Request {request_id!r} not found"
    assert req.decision is not None
    assert req.decision == decision, f"Expected {decision!r}, got {req.decision!r}"


@then('the session decision for "{tool_name}" should be None')
def step_session_decision_none(context, tool_name):
    result = context._service.get_session_decision(tool_name)
    assert result is None, f"Expected None, got {result!r}"


@then('the session decision for "{tool_name}" should be "{decision}"')
def step_session_decision_value(context, tool_name, decision):
    result = context._service.get_session_decision(tool_name)
    assert result == decision, f"Expected {decision!r}, got {result!r}"


@when('I clear the session decision for "{tool_name}"')
def step_clear_session_decision(context, tool_name):
    context._clear_result = context._service.clear_session_decision(tool_name)


@then("the clear result should be False")
def step_clear_result_false(context):
    assert context._clear_result is False


@then("the decision result should be None")
def step_decision_result_none(context):
    assert context._decision_result is None


@then('getting request "{request_id}" should return None')
def step_get_request_none(context, request_id):
    result = context._service.get(request_id)
    assert result is None


@when("I clear all requests")
def step_clear_all(context):
    context._service.clear_all()


# ---------------------------------------------------------------------------
# PermissionsScreen widget
# ---------------------------------------------------------------------------


@given("a fresh PermissionsScreen")
def step_create_permissions_screen(context):
    from cleveragents.tui.permissions.screen import PermissionsScreen

    context._screen = PermissionsScreen()


@when("I load a ToolPermissionRequest with {n:d} file changes")
def step_load_tool_request(context, n):
    req = _make_tool_request("req-screen", "local/file-write", n_changes=n)
    context._screen.load_request(req)


@then('the screen text should contain "{text}"')
def step_screen_text_contains(context, text):
    assert text in context._screen._text, (
        f"Expected {text!r} in screen text:\n{context._screen._text}"
    )


@then("the screen text should be empty")
def step_screen_text_empty(context):
    assert context._screen._text == "", (
        f"Expected empty screen text, got:\n{context._screen._text}"
    )


@then("the selected index should be {index:d}")
def step_selected_index(context, index):
    assert context._screen.selected_index == index, (
        f"Expected index {index}, got {context._screen.selected_index}"
    )


@when("I navigate next on the screen")
def step_navigate_next(context):
    context._screen.navigate_next()


@when("I navigate prev on the screen")
def step_navigate_prev(context):
    context._screen.navigate_prev()


@then('the diff mode should be "{mode}"')
def step_diff_mode(context, mode):
    assert context._screen.diff_mode == mode, (
        f"Expected mode {mode!r}, got {context._screen.diff_mode!r}"
    )


@when("I cycle the diff mode")
def step_cycle_diff_mode(context):
    context._screen.cycle_diff_mode()


@when('I set the diff mode to "{mode}"')
def step_set_diff_mode(context, mode):
    from cleveragents.tui.permissions.models import DiffDisplayMode

    context._screen.set_diff_mode(DiffDisplayMode(mode))


@when("I press allow_once on the screen")
def step_press_allow_once(context):
    context._screen.allow_once()


@when("I press allow_always on the screen")
def step_press_allow_always(context):
    context._screen.allow_always()


@when("I press reject_once on the screen")
def step_press_reject_once(context):
    context._screen.reject_once()


@when("I press reject_always on the screen")
def step_press_reject_always(context):
    context._screen.reject_always()


@then('the screen decision should be "{decision}"')
def step_screen_decision(context, decision):
    assert context._screen.decision == decision, (
        f"Expected decision {decision!r}, got {context._screen.decision!r}"
    )


@then("the screen decision should be None")
def step_screen_decision_none(context):
    assert context._screen.decision is None, (
        f"Expected None, got {context._screen.decision!r}"
    )


@when("I clear the screen")
def step_clear_screen(context):
    context._screen.clear()


# ---------------------------------------------------------------------------
# _next_diff_mode helper
# ---------------------------------------------------------------------------


@when('I call _next_diff_mode with "{mode}"')
def step_call_next_diff_mode(context, mode):
    from cleveragents.tui.permissions.models import DiffDisplayMode
    from cleveragents.tui.permissions.screen import _next_diff_mode

    context._next_mode_result = _next_diff_mode(DiffDisplayMode(mode))


@then('the next diff mode result should be "{mode}"')
def step_next_mode_result(context, mode):
    assert context._next_mode_result == mode, (
        f"Expected {mode!r}, got {context._next_mode_result!r}"
    )


# ---------------------------------------------------------------------------
# Rendering helpers
# ---------------------------------------------------------------------------


@when("I render a file list with {n:d} files and selected index {idx:d}")
def step_render_file_list(context, n, idx):
    from cleveragents.tui.permissions.models import FileChangeType, PermissionRequest
    from cleveragents.tui.permissions.screen import _render_file_list

    changes = [
        PermissionRequest(
            path=f"file{i}.py",
            change_type=FileChangeType.MODIFIED,
            before_content="old\n",
            after_content="new\n",
        )
        for i in range(n)
    ]
    context._file_list_text = _render_file_list(changes, idx)


_ARROW = "\u276f"  # heavy right-pointing angle quotation mark ornament (U+276F)


@then(f'the file list should contain "{_ARROW}" on the second entry')
def step_file_list_arrow_second(context):
    lines = context._file_list_text.splitlines()
    # lines[0] is the header "Files (N changes):"
    # lines[1] is file0 (index 0), lines[2] is file1 (index 1)
    assert _ARROW in lines[2], f"Expected arrow in second file entry, got: {lines[2]!r}"


@when("I render a diff panel with no change")
def step_render_diff_panel_none(context):
    from cleveragents.tui.permissions.models import DiffDisplayMode
    from cleveragents.tui.permissions.screen import _render_diff_panel

    context._diff_panel_text = _render_diff_panel(None, DiffDisplayMode.UNIFIED)


@when("I render a diff panel with an identical-content change")
def step_render_diff_panel_identical(context):
    from cleveragents.tui.permissions.models import DiffDisplayMode
    from cleveragents.tui.permissions.screen import _render_diff_panel

    change = _make_permission_request("src/same.py", "same\n", "same\n")
    context._diff_panel_text = _render_diff_panel(change, DiffDisplayMode.UNIFIED)


@then('the diff panel should contain "{text}"')
def step_diff_panel_contains(context, text):
    assert text in context._diff_panel_text, (
        f"Expected {text!r} in diff panel:\n{context._diff_panel_text}"
    )


@when('I render a status bar for tool "{tool_name}" and resource "{resource_name}"')
def step_render_status_bar(context, tool_name, resource_name):
    from cleveragents.tui.permissions.models import DiffDisplayMode
    from cleveragents.tui.permissions.screen import _render_status_bar

    context._status_bar_text = _render_status_bar(
        tool_name, resource_name, DiffDisplayMode.UNIFIED
    )


@then('the status bar should contain "{text}"')
def step_status_bar_contains(context, text):
    assert text in context._status_bar_text, (
        f"Expected {text!r} in status bar:\n{context._status_bar_text}"
    )


# ---------------------------------------------------------------------------
# PermissionsScreen property accessors
# ---------------------------------------------------------------------------


@then("the screen current_request should be None")
def step_screen_current_request_none(context):
    assert context._screen.current_request is None


@then("the screen current_request should not be None")
def step_screen_current_request_not_none(context):
    assert context._screen.current_request is not None


# ---------------------------------------------------------------------------
# PermissionRequest side_by_side_diff delete/insert branches
# ---------------------------------------------------------------------------


@given("a PermissionRequest with only deleted lines")
def step_create_delete_only_request(context):
    from cleveragents.tui.permissions.models import FileChangeType, PermissionRequest

    context._perm_request = PermissionRequest(
        path="src/deleted.py",
        change_type=FileChangeType.MODIFIED,
        before_content="line1\nline2\nline3\n",
        after_content="",
    )


@given("a PermissionRequest with only inserted lines")
def step_create_insert_only_request(context):
    from cleveragents.tui.permissions.models import FileChangeType, PermissionRequest

    context._perm_request = PermissionRequest(
        path="src/inserted.py",
        change_type=FileChangeType.MODIFIED,
        before_content="",
        after_content="line1\nline2\nline3\n",
    )


@then("the left side should have non-empty lines")
def step_left_side_non_empty(context):
    left, _ = context._perm_request.side_by_side_diff()
    assert any(line.strip() for line in left), f"Expected non-empty left lines: {left}"


@then("the right side should have empty lines for deleted content")
def step_right_side_empty_for_deleted(context):
    _, right = context._perm_request.side_by_side_diff()
    # For delete-only, right side should have empty strings
    assert any(line == "" for line in right), (
        f"Expected empty right lines for deleted content: {right}"
    )


@then("the right side should have non-empty lines")
def step_right_side_non_empty(context):
    _, right = context._perm_request.side_by_side_diff()
    assert any(line.strip() for line in right), (
        f"Expected non-empty right lines: {right}"
    )


@then("the left side should have empty lines for inserted content")
def step_left_side_empty_for_inserted(context):
    left, _ = context._perm_request.side_by_side_diff()
    # For insert-only, left side should have empty strings
    assert any(line == "" for line in left), (
        f"Expected empty left lines for inserted content: {left}"
    )


# ---------------------------------------------------------------------------
# PermissionRequestService clear_session_decision return True
# ---------------------------------------------------------------------------


@then("the clear result should be True")
def step_clear_result_true(context):
    assert context._clear_result is True
