"""Tests for path-parsing sugar (folder/item-key syntax) — Issue #356."""

from __future__ import annotations

import importlib
import json

import pytest
from typer.testing import CliRunner

from cli.commands.items import _validate_folder_intent, parse_item_ref
from cli.config import CONFIG_DIR_ENV, CLIConfig, ConfigStore
from cli.exceptions import CLIError

try:
    from shared import Item, MockBackendClient
except ImportError:
    from cli._vendor.shared import Item, MockBackendClient

from cli.clients import ClientFactory


def _reload_cli_module():
    import cli.main as cli_main

    return importlib.reload(cli_main)


def _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item"):
    """Create a shared MockBackendClient with an item in a specific folder.

    Patches ClientFactory.build to return the same mock instance so state
    persists across CLI invocations within a test.
    """
    from datetime import datetime, timezone
    from uuid import uuid4

    mock = MockBackendClient()
    now = datetime.now(tz=timezone.utc)
    item = Item(
        id=str(uuid4()),
        project_key="core",
        key=item_key,
        title=item_key,
        data="test data",
        tags=[],
        content_sha256="abc123def456",
        content_type="inline",
        folder=folder,
        created_at=now,
        updated_at=now,
    )
    mock._items[("core", item_key)] = item
    monkeypatch.setattr(ClientFactory, "build", lambda self, config: mock)
    return mock


# =============================================================================
# parse_item_ref — unit tests
# =============================================================================


class TestParseItemRef:
    """Unit tests for the parse_item_ref function."""

    # --- Happy path: single-segment (no folder) ---

    def test_single_segment(self):
        """Single segment returns (key, None)."""
        key, folder = parse_item_ref("greeting")
        assert key == "greeting"
        assert folder is None

    def test_single_segment_with_hyphens(self):
        """Single segment with hyphens."""
        key, folder = parse_item_ref("my-item-key")
        assert key == "my-item-key"
        assert folder is None

    # --- Happy path: two-segment ---

    def test_two_segments(self):
        """Two segments: folder + item key."""
        key, folder = parse_item_ref("docs/greeting")
        assert key == "greeting"
        assert folder == "docs"

    def test_two_segments_with_hyphens(self):
        """Two segments with hyphens in both parts."""
        key, folder = parse_item_ref("my-folder/my-key")
        assert key == "my-key"
        assert folder == "my-folder"

    # --- Happy path: deep multi-segment (3+) ---

    def test_three_segments(self):
        """Three segments: deep folder path + item key."""
        key, folder = parse_item_ref("docs/api/greeting")
        assert key == "greeting"
        assert folder == "docs/api"

    def test_four_segments(self):
        """Four segments: even deeper folder path."""
        key, folder = parse_item_ref("docs/api/v2/schema")
        assert key == "schema"
        assert folder == "docs/api/v2"

    def test_five_segments(self):
        """Five segments: very deep nesting."""
        key, folder = parse_item_ref("a/b/c/d/item")
        assert key == "item"
        assert folder == "a/b/c/d"

    # --- Invalid: empty input ---

    def test_empty_string_raises(self):
        """Empty string raises CLIError."""
        with pytest.raises(CLIError, match="must not be empty"):
            parse_item_ref("")

    def test_whitespace_only_raises(self):
        """Whitespace-only string raises CLIError."""
        with pytest.raises(CLIError, match="must not be empty"):
            parse_item_ref("   ")

    # --- Invalid: leading slash ---

    def test_leading_slash_raises(self):
        """Leading slash raises CLIError."""
        with pytest.raises(CLIError, match="leading slash"):
            parse_item_ref("/docs/greeting")

    def test_leading_slash_single_segment(self):
        """Leading slash with single segment raises CLIError."""
        with pytest.raises(CLIError, match="leading slash"):
            parse_item_ref("/greeting")

    # --- Invalid: trailing slash ---

    def test_trailing_slash_raises(self):
        """Trailing slash raises CLIError."""
        with pytest.raises(CLIError, match="trailing slash"):
            parse_item_ref("docs/greeting/")

    def test_trailing_slash_single_segment(self):
        """Trailing slash with single segment raises CLIError."""
        with pytest.raises(CLIError, match="trailing slash"):
            parse_item_ref("greeting/")

    # --- Invalid: empty segments ---

    def test_double_slash_raises(self):
        """Double slash (empty segment) raises CLIError."""
        with pytest.raises(CLIError, match="empty segment"):
            parse_item_ref("docs//greeting")

    def test_triple_slash_raises(self):
        """Triple slash (empty segments) raises CLIError."""
        with pytest.raises(CLIError, match="empty segment"):
            parse_item_ref("docs///greeting")

    # --- Invalid: whitespace segments ---

    def test_whitespace_segment_raises(self):
        """Whitespace-only segment raises CLIError."""
        with pytest.raises(CLIError, match="whitespace-only segment"):
            parse_item_ref("docs/ /greeting")

    def test_tab_segment_raises(self):
        """Tab-only segment raises CLIError."""
        with pytest.raises(CLIError, match="whitespace-only segment"):
            parse_item_ref("docs/\t/greeting")


# =============================================================================
# _resolve_item_ref — conflict resolution tests
# =============================================================================


class TestResolveItemRef:
    """Tests for _resolve_item_ref conflict resolution with --folder."""

    def _resolve(self, item_key: str, folder_option: str | None) -> tuple[str, str | None]:
        from cli.commands.items import _resolve_item_ref

        return _resolve_item_ref(item_key, folder_option=folder_option)

    def test_no_folder_no_option(self):
        """Single segment with no --folder returns (key, None)."""
        key, folder = self._resolve("greeting", None)
        assert key == "greeting"
        assert folder is None

    def test_path_sugar_no_option(self):
        """Path sugar with no --folder returns parsed folder."""
        key, folder = self._resolve("docs/greeting", None)
        assert key == "greeting"
        assert folder == "docs"

    def test_no_sugar_with_option(self):
        """Single segment with --folder returns option folder."""
        key, folder = self._resolve("greeting", "docs")
        assert key == "greeting"
        assert folder == "docs"

    def test_redundant_match_allowed(self):
        """Path sugar matching --folder is allowed (redundant)."""
        key, folder = self._resolve("docs/greeting", "docs")
        assert key == "greeting"
        assert folder == "docs"

    def test_deep_path_redundant_match(self):
        """Deep path sugar matching --folder is allowed."""
        key, folder = self._resolve("docs/api/greeting", "docs/api")
        assert key == "greeting"
        assert folder == "docs/api"

    def test_mismatch_raises(self):
        """Path sugar conflicting with --folder raises CLIError."""
        with pytest.raises(CLIError, match="Conflicting folder"):
            self._resolve("docs/greeting", "archive")

    def test_deep_mismatch_raises(self):
        """Deep path sugar conflicting with --folder raises CLIError."""
        with pytest.raises(CLIError, match="Conflicting folder"):
            self._resolve("docs/api/greeting", "other/path")


# =============================================================================
# CLI integration tests — supported commands
# =============================================================================


class TestGetPathSugar:
    """Integration tests for 'ctx get' with path sugar."""

    def test_get_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx get docs/test-item extracts key and validates folder."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "docs/test-item", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["folder"] == "docs"

    def test_get_with_deep_path(self, tmp_path, monkeypatch):
        """ctx get docs/api/test-item extracts key and validates deep folder."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        _setup_mock_with_folder_item(monkeypatch, folder="docs/api", item_key="test-item")
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "docs/api/test-item", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["folder"] == "docs/api"

    def test_get_single_segment_unchanged(self, tmp_path, monkeypatch):
        """ctx get welcome works without path sugar (backward compat)."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "welcome", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "welcome"


class TestReadPathSugar:
    """Integration tests for 'ctx read' with path sugar."""

    def test_read_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx read docs/test-item extracts key and validates folder."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["read", "docs/test-item", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"


class TestDeletePathSugar:
    """Integration tests for 'ctx delete' / 'ctx rm' with path sugar."""

    def test_delete_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx delete docs/test-item --force extracts key and validates folder."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["delete", "docs/test-item", "--force"])

        assert result.exit_code == 0
        assert "Deleted" in result.stdout

    def test_delete_json_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx delete docs/test-item --json returns extracted key."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["delete", "docs/test-item", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["status"] == "deleted"

    def test_rm_alias_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx rm docs/test-item --force extracts key (alias coverage)."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["rm", "docs/test-item", "--force"])

        assert result.exit_code == 0
        assert "Deleted" in result.stdout


class TestCreatePathSugar:
    """Integration tests for 'ctx create' / 'ctx put' with path sugar."""

    def test_create_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx create docs/test-item --data 'x' sets folder from path sugar."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(
            cli_main.app, ["create", "docs/test-item", "--data", "hello", "--json"]
        )

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["folder"] == "docs"

    def test_create_deep_path_sugar(self, tmp_path, monkeypatch):
        """ctx create docs/api/schema --data 'x' sets deep folder from path sugar."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(
            cli_main.app, ["create", "docs/api/schema", "--data", "hello", "--json"]
        )

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "schema"
        assert data["folder"] == "docs/api"

    def test_create_folder_redundant_match(self, tmp_path, monkeypatch):
        """ctx create docs/test-item --data 'x' --folder docs succeeds (redundant)."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(
            cli_main.app,
            ["create", "docs/test-item", "--data", "hello", "--folder", "docs", "--json"],
        )

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["folder"] == "docs"

    def test_create_folder_mismatch_errors(self, tmp_path, monkeypatch):
        """ctx create docs/test-item --folder archive errors on conflict."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(
            cli_main.app,
            ["create", "docs/test-item", "--data", "hello", "--folder", "archive"],
        )

        assert result.exit_code == 1
        assert "Conflicting folder" in str(result.exception)

    def test_put_alias_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx put docs/test-item --data 'x' works (alias coverage)."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["put", "docs/test-item", "--data", "hello", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["folder"] == "docs"


class TestUpdatePathSugar:
    """Integration tests for 'ctx update' with path sugar."""

    def test_update_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx update docs/test-item --title 'New' extracts key and validates folder."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["update", "docs/test-item", "--title", "New Title"])

        assert result.exit_code == 0
        assert "Updated" in result.stdout


class TestMvPathSugar:
    """Integration tests for 'ctx mv' with path sugar."""

    def test_mv_with_path_sugar(self, tmp_path, monkeypatch):
        """ctx mv docs/test-item --folder archive extracts key and validates folder."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(
            cli_main.app, ["mv", "docs/test-item", "--folder", "archive", "--json"]
        )

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["status"] == "moved"


# =============================================================================
# Unsupported commands — path sugar should NOT apply
# =============================================================================


class TestUnsupportedCommands:
    """Verify path sugar is NOT applied to search, list, folders, upload."""

    def test_search_does_not_parse_slashes(self, tmp_path, monkeypatch):
        """ctx search 'docs/greeting' treats entire string as query (not path sugar)."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["search", "docs/greeting"])

        # Should succeed — the slash is part of the query, not parsed as path sugar
        assert result.exit_code == 0

    def test_ls_does_not_parse_slashes(self, tmp_path, monkeypatch):
        """ctx ls has no positional item-key arg, so no path sugar to apply."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["ls"])

        assert result.exit_code == 0

    def test_upload_does_not_parse_path_as_item_ref(self, tmp_path, monkeypatch):
        """ctx upload <path> treats positional arg as file path, not item ref."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        # Create a test file at a path containing slashes (subdir)
        subdir = tmp_path / "testdir"
        subdir.mkdir()
        test_file = subdir / "test.txt"
        test_file.write_text("hello")

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["upload", str(test_file)])

        # Should try to upload the file, not parse the path as item-ref sugar
        # The mock client will handle the upload
        assert result.exit_code == 0

    def test_up_alias_does_not_parse_path(self, tmp_path, monkeypatch):
        """ctx up <path> (alias) treats positional arg as file path, not item ref."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        subdir = tmp_path / "testdir"
        subdir.mkdir()
        test_file = subdir / "test.txt"
        test_file.write_text("hello")

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["up", str(test_file)])

        assert result.exit_code == 0


# =============================================================================
# Backward compatibility
# =============================================================================


class TestBackwardCompatibility:
    """Ensure single-segment keys continue to work identically."""

    def test_get_single_segment(self, tmp_path, monkeypatch):
        """ctx get welcome (no slash) works exactly as before."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "welcome", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "welcome"

    def test_create_single_segment(self, tmp_path, monkeypatch):
        """ctx create new-item --data 'x' (no slash) works with folder=None."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["create", "new-item", "--data", "hello", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "new-item"

    def test_delete_single_segment(self, tmp_path, monkeypatch):
        """ctx delete welcome --force (no slash) works exactly as before."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["delete", "welcome", "--force"])

        assert result.exit_code == 0
        assert "Deleted" in result.stdout


# =============================================================================
# Malformed input tests (CLI integration)
# =============================================================================


class TestMalformedInputCLI:
    """CLI-level tests for malformed path-sugar input."""

    def test_get_trailing_slash_errors(self, tmp_path, monkeypatch):
        """ctx get docs/greeting/ errors with trailing slash message."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "docs/greeting/"])

        assert result.exit_code == 1
        assert "trailing slash" in str(result.exception)

    def test_get_leading_slash_errors(self, tmp_path, monkeypatch):
        """ctx get /docs/greeting errors with leading slash message."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "/docs/greeting"])

        assert result.exit_code == 1
        assert "leading slash" in str(result.exception)

    def test_get_double_slash_errors(self, tmp_path, monkeypatch):
        """ctx get docs//greeting errors with empty segment message."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "docs//greeting"])

        assert result.exit_code == 1
        assert "empty segment" in str(result.exception)


# =============================================================================
# _validate_folder_intent — unit tests
# =============================================================================


class TestValidateFolderIntent:
    """Tests for folder mismatch detection between path sugar and actual item folder."""

    def test_none_parsed_folder_is_noop(self):
        """No parsed folder (single-segment ref) skips validation."""
        _validate_folder_intent(parsed_folder=None, actual_folder="docs", item_key="welcome")

    def test_matching_folder_succeeds(self):
        """Parsed folder matches actual folder — no error."""
        _validate_folder_intent(parsed_folder="docs", actual_folder="docs", item_key="welcome")

    def test_deep_matching_folder_succeeds(self):
        """Deep parsed folder matches actual folder — no error."""
        _validate_folder_intent(
            parsed_folder="docs/api", actual_folder="docs/api", item_key="schema"
        )

    def test_mismatch_raises(self):
        """Parsed folder != actual folder raises CLIError with remediation."""
        with pytest.raises(CLIError, match="Folder mismatch"):
            _validate_folder_intent(
                parsed_folder="docs", actual_folder="archive", item_key="welcome"
            )

    def test_mismatch_item_in_root(self):
        """Parsed folder with item in root (None) raises CLIError."""
        with pytest.raises(CLIError, match="Folder mismatch"):
            _validate_folder_intent(parsed_folder="docs", actual_folder=None, item_key="welcome")

    def test_mismatch_remediation_message(self):
        """Mismatch error includes only valid remediation (no '(root)/item' suggestion)."""
        with pytest.raises(CLIError, match=r"Use 'ctx get welcome' instead\.") as exc_info:
            _validate_folder_intent(parsed_folder="docs", actual_folder=None, item_key="welcome")
        # Must NOT suggest '(root)/welcome' — that's not valid path-sugar syntax
        assert "(root)/welcome" not in str(exc_info.value)

    def test_mismatch_remediation_with_actual_folder(self):
        """Mismatch error includes correct actual folder path in remediation."""
        with pytest.raises(CLIError, match=r"ctx get archive/welcome"):
            _validate_folder_intent(
                parsed_folder="docs", actual_folder="archive", item_key="welcome"
            )


# =============================================================================
# Folder mismatch remediation — CLI integration tests
# =============================================================================


def _setup_item_in_folder(mock_client, project_key, item_key, folder):
    """Insert a mock item in a specific folder for testing."""
    from datetime import datetime, timezone
    from uuid import uuid4

    try:
        from shared import Item
    except ImportError:
        from cli._vendor.shared import Item

    item = Item(
        id=str(uuid4()),
        project_key=project_key,
        key=item_key,
        title=item_key,
        data="test data",
        tags=[],
        content_sha256="abc123",
        content_type="inline",
        folder=folder,
        created_at=datetime.now(tz=timezone.utc),
        updated_at=datetime.now(tz=timezone.utc),
    )
    mock_client._items[(project_key, item_key)] = item
    return item


class TestFolderMismatchRemediation:
    """CLI integration tests for folder mismatch detection across commands."""

    def _setup(self, tmp_path, monkeypatch):
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        cli_main = _reload_cli_module()
        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))
        return cli_main

    def test_get_folder_mismatch_errors(self, tmp_path, monkeypatch):
        """ctx get archive/welcome errors when item is in 'docs' folder."""
        cli_main = self._setup(tmp_path, monkeypatch)
        # Item 'welcome' is in folder=None (root) in mock client
        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "archive/welcome", "--json"])

        assert result.exit_code == 1
        assert "Folder mismatch" in str(result.exception)

    def test_get_matching_folder_succeeds(self, tmp_path, monkeypatch):
        """ctx get docs/test-item succeeds when item is in 'docs' folder."""
        monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
        _setup_mock_with_folder_item(monkeypatch, folder="docs", item_key="test-item")
        cli_main = _reload_cli_module()

        store = ConfigStore()
        store.save(CLIConfig(default_project="core", use_mock_client=True))

        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["get", "docs/test-item", "--json"])
        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["item_key"] == "test-item"
        assert data["folder"] == "docs"

    def test_read_folder_mismatch_errors(self, tmp_path, monkeypatch):
        """ctx read archive/welcome errors when item is in root folder."""
        cli_main = self._setup(tmp_path, monkeypatch)
        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["read", "archive/welcome", "--json"])

        assert result.exit_code == 1
        assert "Folder mismatch" in str(result.exception)

    def test_delete_folder_mismatch_errors(self, tmp_path, monkeypatch):
        """ctx delete archive/welcome --force errors when item is in root folder."""
        cli_main = self._setup(tmp_path, monkeypatch)
        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["delete", "archive/welcome", "--force"])

        assert result.exit_code == 1
        assert "Folder mismatch" in str(result.exception)

    def test_update_folder_mismatch_errors(self, tmp_path, monkeypatch):
        """ctx update archive/welcome --title 'X' errors when item is in root."""
        cli_main = self._setup(tmp_path, monkeypatch)
        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["update", "archive/welcome", "--title", "New Title"])

        assert result.exit_code == 1
        assert "Folder mismatch" in str(result.exception)

    def test_mv_folder_mismatch_errors(self, tmp_path, monkeypatch):
        """ctx mv archive/welcome --folder other errors when item is in root."""
        cli_main = self._setup(tmp_path, monkeypatch)
        runner = CliRunner()
        result = runner.invoke(cli_main.app, ["mv", "archive/welcome", "--folder", "other"])

        assert result.exit_code == 1
        assert "Folder mismatch" in str(result.exception)
