"""Tests for folder-related CLI features (list, create, update, move, folders command)."""

from __future__ import annotations

import importlib
import json
import re
import sys

import pytest
from typer.testing import CliRunner

from cli.config import CONFIG_DIR_ENV, CLIConfig, ConfigStore


def _reload_cli_module():
    import cli.main as cli_main

    return importlib.reload(cli_main)


# =============================================================================
# list_items --folder / --recursive
# =============================================================================


def test_ls_folder_option(tmp_path, monkeypatch):
    """ctx ls --folder docs passes folder param to list_items."""
    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", "--folder", "docs"])

    assert result.exit_code == 0
    # No items in that folder is fine - just confirm it didn't error out
    # The mock backend will filter by folder and likely return empty
    assert result.exit_code == 0


def test_ls_recursive_without_folder_errors(tmp_path, monkeypatch):
    """ctx ls --recursive without --folder raises error."""
    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", "--recursive"])

    assert result.exit_code == 1
    assert "--recursive requires --folder" in str(result.exception)


def test_ls_folder_with_all_errors(tmp_path, monkeypatch):
    """ctx ls --folder x --all raises error."""
    monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
    cli_main = _reload_cli_module()

    store = ConfigStore()
    store.save(CLIConfig(use_mock_client=True))

    runner = CliRunner()
    result = runner.invoke(cli_main.app, ["ls", "--folder", "x", "--all"])

    assert result.exit_code == 1
    assert "Cannot use --folder with --all" in str(result.exception)


def test_ls_json_includes_folder(tmp_path, monkeypatch):
    """JSON output from ls includes folder field in each item."""
    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", "--json"])

    assert result.exit_code == 0
    data = json.loads(result.stdout)
    assert "items" in data
    # Every item in the response should have a 'folder' key
    for item in data["items"]:
        assert "folder" in item


@pytest.mark.parametrize(
    ("command", "expected_substrings"),
    [
        (
            "create",
            [
                "folder/item-key path",
                "'docs/greeting'",
                "folder path (e.g. 'docs').",
                "format/depth",
                "enforced by the backend",
            ],
        ),
        (
            "list",
            [
                "Filter by folder path.",
                "validation is enforced by the backend",
                "requires --folder filter",
            ],
        ),
        (
            "update",
            [
                "move item to folder path",
                "'docs'",
                "moves item to root",
                "format/depth",
                "enforced by the backend",
            ],
        ),
        (
            "mv",
            [
                "folder/item-key path",
                "'docs/greeting'",
                "target folder path",
                "moves item to root",
                "format/depth",
                "enforced by the backend",
            ],
        ),
        (
            "folders",
            [
                "folder path validity is backend-defined",
                "direct children",
                "folder path",
                "validation is enforced by the backend",
            ],
        ),
    ],
)
def test_folder_help_text_mentions_backend_validation(command: str, expected_substrings: list[str]):
    """Folder-related help text communicates backend-authoritative path validation."""
    cli_main = _reload_cli_module()
    runner = CliRunner()
    result = runner.invoke(cli_main.app, [command, "--help"])

    assert result.exit_code == 0
    cleaned_output = re.sub(r"\x1b\[[0-9;]*m", "", result.stdout.lower())
    cleaned_output = re.sub(r"[│╭╮╰╯─]+", " ", cleaned_output)
    normalized_output = " ".join(cleaned_output.split())
    for expected in expected_substrings:
        assert " ".join(expected.lower().split()) in normalized_output


# =============================================================================
# folders command
# =============================================================================


def test_folders_command(tmp_path, monkeypatch):
    """ctx folders lists folders (mock returns empty)."""
    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, ["folders"])

    assert result.exit_code == 0
    # The mock backend returns empty folders list
    assert "No folders found" in result.stdout


def test_folders_parent_option(tmp_path, monkeypatch):
    """ctx folders --parent docs passes parent param."""
    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, ["folders", "--parent", "docs"])

    assert result.exit_code == 0
    # Mock returns empty, just verify it didn't error
    assert "No folders found" in result.stdout


def test_folders_json_output(tmp_path, monkeypatch):
    """ctx folders --json returns structured JSON with folders key."""
    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, ["folders", "--json"])

    assert result.exit_code == 0
    data = json.loads(result.stdout)
    assert "folders" in data
    assert data["folders"] == []
    assert data["has_more"] is False
    assert data["project_key"] == "core"


# =============================================================================
# put/create --folder
# =============================================================================


def test_put_folder_option(tmp_path, monkeypatch):
    """ctx create item --data 'x' --folder docs passes folder."""
    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", "test-item", "--data", "hello", "--folder", "docs"]
    )

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


def test_put_json_includes_folder(tmp_path, monkeypatch):
    """JSON output from create includes folder field."""
    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", "test-item", "--data", "hello", "--folder", "docs", "--json"],
    )

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


# =============================================================================
# update --folder
# =============================================================================


def test_update_folder_option(tmp_path, monkeypatch):
    """ctx update item --folder docs works."""
    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, ["update", "welcome", "--folder", "docs"])

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


def test_update_includes_folder_in_has_updates(tmp_path, monkeypatch):
    """--folder is recognized as an update flag (not treated as no-op)."""
    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()
    # With --folder, should succeed (recognized as update)
    result = runner.invoke(cli_main.app, ["update", "welcome", "--folder", "docs", "--json"])

    assert result.exit_code == 0
    data = json.loads(result.stdout)
    assert data["status"] == "updated"
    assert data["folder"] == "docs"


def test_update_without_flags_errors(tmp_path, monkeypatch):
    """ctx update item without any flags shows error about missing flags including --folder."""
    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, ["update", "welcome"])

    assert result.exit_code == 1
    # The error message should mention --folder as one of the valid flags
    assert "--folder" in str(result.exception)


# =============================================================================
# mv command
# =============================================================================


def test_mv_command(tmp_path, monkeypatch):
    """ctx mv item --folder docs works."""
    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, ["mv", "welcome", "--folder", "docs"])

    assert result.exit_code == 0
    assert "Moved" in result.stdout
    assert "docs" in result.stdout


def test_mv_json_output(tmp_path, monkeypatch):
    """ctx mv item --folder docs --json returns structured JSON."""
    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, ["mv", "welcome", "--folder", "docs", "--json"])

    assert result.exit_code == 0
    data = json.loads(result.stdout)
    assert data["status"] == "moved"
    assert data["item_key"] == "welcome"
    assert data["folder"] == "docs"
    assert data["project_key"] == "core"


def test_mv_to_root(tmp_path, monkeypatch):
    """ctx mv item --folder '' moves item to root (removes from folder)."""
    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, ["mv", "welcome", "--folder", "", "--json"])

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


# =============================================================================
# FeatureDisabledError handling
# =============================================================================


def _reload_cli_module_with_env(monkeypatch, tmp_path):
    """Reload CLI module with proper env setup (matching test_error_handling pattern)."""
    monkeypatch.setenv(CONFIG_DIR_ENV, str(tmp_path))
    import cli.main as cli_main

    return importlib.reload(cli_main)


def _set_argv(monkeypatch, args: list[str]) -> None:
    monkeypatch.setattr(sys, "argv", ["ctxme", *args])


def test_feature_disabled_error_message(tmp_path, monkeypatch, capsys):
    """FeatureDisabledError shows 'Feature X is not enabled' message (not 'Not signed in')."""
    try:
        from shared import FeatureDisabledError
    except ImportError:
        from cli._vendor.shared import FeatureDisabledError

    cli_main = _reload_cli_module_with_env(monkeypatch, tmp_path)
    monkeypatch.delenv("CTXME_DEBUG", raising=False)

    error = FeatureDisabledError(
        "Folders feature is not enabled for this account.",
        feature="folders",
        hint="Upgrade to a paid plan to access folders.",
        status_code=403,
        request_id="req-feature-123",
    )

    def _fail(*_args, **_kwargs):
        raise error

    monkeypatch.setattr(cli_main, "app", _fail)
    _set_argv(monkeypatch, ["list"])

    with pytest.raises(SystemExit) as excinfo:
        cli_main.main()

    assert excinfo.value.code == 1
    captured = capsys.readouterr()
    # Should show feature-specific message, NOT "Not signed in"
    assert "Feature 'folders' is not enabled" in captured.err
    assert "Not signed in" not in captured.err


def test_feature_disabled_json_error(tmp_path, monkeypatch, capsys):
    """FeatureDisabledError returns proper JSON error with feature_disabled code."""
    try:
        from shared import FeatureDisabledError
    except ImportError:
        from cli._vendor.shared import FeatureDisabledError

    from cli.options import OutputMode

    cli_main = _reload_cli_module_with_env(monkeypatch, tmp_path)

    error = FeatureDisabledError(
        "Folders feature is not enabled for this account.",
        feature="folders",
        hint="Upgrade to a paid plan to access folders.",
        status_code=403,
        request_id="req-feature-456",
    )

    def _fail(*_args, **_kwargs):
        raise error

    monkeypatch.setattr(cli_main, "app", _fail)
    monkeypatch.setattr(cli_main, "_output_mode", OutputMode.JSON)
    _set_argv(monkeypatch, ["--output", "json", "list"])

    with pytest.raises(SystemExit) as excinfo:
        cli_main.main()

    assert excinfo.value.code == 1
    captured = capsys.readouterr()
    error_json = json.loads(captured.out)

    assert error_json["error"] == "feature_disabled"
    assert "folders" in error_json["message"].lower()
    assert error_json["status_code"] == 403
    assert error_json["request_id"] == "req-feature-456"
    # Should use the hint from the exception
    assert "Upgrade" in error_json["hint"] or "folders" in error_json["hint"]
    # Should include feature identifier in context
    assert error_json["context"]["feature"] == "folders"
    # stderr should be empty in JSON mode
    assert captured.err == ""
