"""
Module: test_integration
Description: End-to-end integration tests for CLI workflows

Implements:
    - docs/cli/reference.md#testing
"""

import os
from pathlib import Path
from unittest.mock import patch

import pytest
from typer.testing import CliRunner

from huitzo_cli.main import app


def test_pack_creation_workflow(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test complete pack creation workflow.

    1. init pack
    2. validate pack
    3. test pack
    """
    from huitzo_cli.config import ConfigManager

    runner = CliRunner()

    # Mock config dir to avoid hitting real quota file
    config_dir = temp_dir / "config"
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: config_dir))

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)

        # 1. Init pack - provide all required arguments to avoid prompts
        result = runner.invoke(
            app,
            [
                "pack",
                "new",
                "test-pack",
                "--namespace",
                "test",
                "--author",
                "Test",
                "--email",
                "test@example.com",
                "--description",
                "Test Pack",
            ],
            input="n\n",  # Decline Claude Code setup
        )
        assert result.exit_code == 0
        assert "initialized" in result.stdout

        # 2. Validate pack
        os.chdir(temp_dir / "test-pack")
        result = runner.invoke(app, ["pack", "validate"])
        # May have warnings but should be valid
        assert "Validation:" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_help_commands(cli_runner: CliRunner) -> None:
    """Test all help commands."""
    result = cli_runner.invoke(app, ["--help"])
    assert result.exit_code == 0
    assert "pack" in result.stdout
    assert "login" in result.stdout
    assert "run" in result.stdout


def test_version_command(cli_runner: CliRunner) -> None:
    """Test version command."""
    result = cli_runner.invoke(app, ["--version"])
    assert result.exit_code == 0
    assert "huitzo-cli" in result.stdout


def test_config_workflow(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test configuration workflow."""
    from huitzo_cli.config import ConfigManager

    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: temp_dir / "config"))

    # Set value
    result = cli_runner.invoke(app, ["config", "set", "api_url", "http://localhost:8000"])
    assert result.exit_code == 0

    # Get value
    result = cli_runner.invoke(app, ["config", "get", "api_url"])
    assert result.exit_code == 0
    assert "http://localhost:8000" in result.stdout


def test_validation_on_invalid_pack(temp_dir: Path) -> None:
    """Test validation fails on invalid pack."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)

        # Try to validate empty directory
        result = runner.invoke(app, ["pack", "validate"])
        # Exit code 1 for validation failure
        assert result.exit_code == 1
        assert "pyproject.toml" in result.stdout or "not found" in result.stdout.lower()

    finally:
        os.chdir(old_cwd)


def test_secrets_workflow(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test secrets workflow."""
    from huitzo_cli.commands.secrets import SecretsStore

    monkeypatch.setattr(
        SecretsStore, "_get_secrets_dir", staticmethod(lambda: temp_dir / "secrets")
    )

    with patch("huitzo_cli.commands.secrets._keyring_available", return_value=False):
        # Set secret (now requires interactive prompt)
        result = cli_runner.invoke(app, ["secrets", "set", "api_key"], input="secret123\n")
        assert result.exit_code == 0

        # List secrets
        result = cli_runner.invoke(app, ["secrets", "list"])
        assert result.exit_code == 0
        assert "api_key" in result.stdout

        # Remove secret
        result = cli_runner.invoke(app, ["secrets", "remove", "api_key"])
        assert result.exit_code == 0


def test_auth_workflow(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test authentication workflow."""
    from huitzo_cli.auth import TokenStore
    from huitzo_cli.config import ConfigManager

    token_file = temp_dir / "token"
    config_dir = temp_dir / "config"

    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: config_dir))

    # Login via HUITZO_AUTH_TOKEN env var (--token flag was removed)
    monkeypatch.setenv("HUITZO_AUTH_TOKEN", "test-token")
    result = cli_runner.invoke(app, ["login"])
    assert result.exit_code == 0
    assert "Logged in" in result.stdout

    # Logout (top-level, no auth subgroup)
    result = cli_runner.invoke(app, ["logout"])
    assert result.exit_code == 0
    assert "Logged out" in result.stdout


def test_publish_requires_manifest(cli_runner: CliRunner, temp_dir: Path) -> None:
    """Test that publish requires huitzo.yaml first."""
    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        result = cli_runner.invoke(app, ["pack", "publish"])
        assert result.exit_code == 1
        assert "huitzo.yaml" in result.stdout
    finally:
        os.chdir(old_cwd)


def test_dev_validates_pack(cli_runner: CliRunner) -> None:
    """Test dev fails on invalid pack."""
    with patch("huitzo_cli.commands.dev.PackValidator") as mock_validator:
        mock_validator.return_value.validate.return_value.valid = False
        mock_validator.return_value.validate.return_value.errors = []

        result = cli_runner.invoke(app, ["pack", "dev"])
        # Exit code 1 for validation failure
        assert result.exit_code in (1, 2)


# --- E2E Tests for huitzo pack dev proxy workflow ---


@pytest.mark.e2e
def test_dev_proxy_lifecycle() -> None:
    """Test complete dev session lifecycle: start proxy, execute commands, shutdown.

    This E2E test verifies:
    1. Local proxy starts successfully
    2. Health endpoint works
    3. Commands endpoint requires auth
    4. Proper cleanup on shutdown
    """
    import httpx
    import threading
    import time
    from huitzo_cli.sandbox.proxy import LocalSandbox

    # 1. Start local proxy sandbox (no-TLS mode disables auth)
    sandbox = LocalSandbox()

    # Register a test command manually
    async def test_cmd(args: dict, ctx):
        return {"result": "test", "args": args}

    sandbox._commands["test-cmd"] = test_cmd
    sandbox._metadata["test-cmd"] = {
        "name": "test-cmd",
        "namespace": "test/test-cmd",
        "description": "Test command",
    }

    # Start proxy in background thread
    proxy_url = None
    proxy_error = None

    def start_proxy():
        nonlocal proxy_url, proxy_error
        try:
            proxy_url = sandbox.start(port=0, use_tls=False)  # Use dynamic port for testing
        except Exception as e:
            proxy_error = e

    proxy_thread = threading.Thread(target=start_proxy, daemon=True)
    proxy_thread.start()

    # Wait for proxy to start with retry logic
    max_wait = 10  # seconds
    start_time = time.time()
    proxy_ready = False

    while time.time() - start_time < max_wait:
        if proxy_error:
            raise proxy_error
        # Check if proxy_url was set AND health check succeeds
        if proxy_url is not None:
            try:
                httpx.get(f"{proxy_url}/health", timeout=1.0)
                proxy_ready = True
                break
            except (httpx.ConnectError, httpx.TimeoutException):
                pass
        time.sleep(0.5)

    if proxy_error:
        raise proxy_error
    elif not proxy_ready:
        pytest.fail(f"Proxy failed to start within timeout (proxy_url={proxy_url})")

    assert proxy_url is not None, "Proxy URL was not set"

    # 2. Test proxy endpoints
    try:
        # Health check (no auth required)
        response = httpx.get(f"{proxy_url}/health", timeout=5.0)
        assert response.status_code == 200
        assert response.json()["status"] == "ok"

        # List commands (no auth required in no-TLS mode)
        response = httpx.get(
            f"{proxy_url}/commands",
            timeout=5.0,
        )
        assert response.status_code == 200
        data = response.json()
        assert "commands" in data
        assert len(data["commands"]) == 1
        assert data["commands"][0]["name"] == "test-cmd"

        # Execute command
        response = httpx.post(
            f"{proxy_url}/commands/test-cmd",
            json={"args": {"foo": "bar"}},
            timeout=10.0,
        )
        assert response.status_code == 200
        result = response.json()
        assert result["data"]["result"] == "test"
        assert result["data"]["args"]["foo"] == "bar"

    finally:
        # 3. Cleanup
        sandbox.stop()

        # Wait for proxy to actually stop (with retry)
        max_wait = 5  # seconds
        start_time = time.time()
        proxy_stopped = False

        while time.time() - start_time < max_wait:
            try:
                httpx.get(f"{proxy_url}/health", timeout=0.5)
                time.sleep(0.5)
            except (httpx.ConnectError, httpx.TimeoutException):
                proxy_stopped = True
                break

        if not proxy_stopped:
            pytest.fail("Proxy still responding after stop()")


@pytest.mark.e2e
def test_dev_proxy_authentication() -> None:
    """Test that no-TLS mode disables auth enforcement (C4/W23 security fix).

    When use_tls=False, auth tokens are NOT enforced to ensure developers
    understand the security implications. Auth is only enforced with TLS.
    """
    import httpx
    import threading
    import time
    from huitzo_cli.sandbox.proxy import LocalSandbox

    # Start proxy without TLS — auth_token should be disabled
    sandbox = LocalSandbox(auth_token="secret-token-123")

    # Add a test command
    async def test_cmd(args: dict, ctx):
        return {"result": "ok"}

    sandbox._commands["test"] = test_cmd
    sandbox._metadata["test"] = {"name": "test", "namespace": "test/test"}

    proxy_url = None
    proxy_error = None

    def start_proxy():
        nonlocal proxy_url, proxy_error
        try:
            proxy_url = sandbox.start(port=0, use_tls=False)  # No TLS = no auth
        except Exception as e:
            proxy_error = e

    proxy_thread = threading.Thread(target=start_proxy, daemon=True)
    proxy_thread.start()

    # Wait for proxy to be ready
    max_wait = 10
    start_time = time.time()
    proxy_ready = False

    while time.time() - start_time < max_wait:
        if proxy_error:
            raise proxy_error
        if proxy_url is not None:
            try:
                httpx.get(f"{proxy_url}/health", timeout=1.0)
                proxy_ready = True
                break
            except (httpx.ConnectError, httpx.TimeoutException):
                pass
        time.sleep(0.5)

    if proxy_error:
        raise proxy_error
    elif not proxy_ready:
        pytest.fail(f"Proxy failed to start within timeout (proxy_url={proxy_url})")

    assert proxy_url is not None, "Proxy URL was not set"

    try:
        # No-TLS mode should allow unauthenticated access
        response = httpx.get(f"{proxy_url}/commands", timeout=5.0)
        assert response.status_code == 200

        # Auth token should have been cleared by no-TLS mode
        assert sandbox._auth_token is None

    finally:
        sandbox.stop()


@pytest.mark.e2e
def test_dev_proxy_command_execution() -> None:
    """Test executing commands through the proxy."""
    import httpx
    import threading
    import time
    from huitzo_cli.sandbox.proxy import LocalSandbox

    # Start proxy
    sandbox = LocalSandbox()  # No auth in no-TLS mode

    # Add test command
    async def test_echo(args: dict, ctx):
        message = args.get("message", "default")
        return {"echo": message, "success": True}

    sandbox._commands["test-echo"] = test_echo
    sandbox._metadata["test-echo"] = {
        "name": "test-echo",
        "namespace": "test/test-echo",
        "description": "Echo command",
    }

    proxy_url = None
    proxy_error = None

    def start_proxy():
        nonlocal proxy_url, proxy_error
        try:
            proxy_url = sandbox.start(port=0, use_tls=False)  # Use dynamic port for testing
        except Exception as e:
            proxy_error = e

    proxy_thread = threading.Thread(target=start_proxy, daemon=True)
    proxy_thread.start()

    # Wait for proxy to be ready
    max_wait = 10
    start_time = time.time()
    proxy_ready = False

    while time.time() - start_time < max_wait:
        if proxy_error:
            raise proxy_error
        if proxy_url is not None:
            try:
                httpx.get(f"{proxy_url}/health", timeout=1.0)
                proxy_ready = True
                break
            except (httpx.ConnectError, httpx.TimeoutException):
                pass
        time.sleep(0.5)

    if proxy_error:
        raise proxy_error
    elif not proxy_ready:
        pytest.fail(f"Proxy failed to start within timeout (proxy_url={proxy_url})")

    assert proxy_url is not None, "Proxy URL was not set"

    try:
        # Execute test command (no auth needed in no-TLS mode)
        response = httpx.post(
            f"{proxy_url}/commands/test-echo",
            json={"args": {"message": "Hello E2E!"}},
            timeout=10.0,
        )

        assert response.status_code == 200, f"Command failed: {response.text}"
        result_data = response.json()

        assert "data" in result_data
        assert result_data["data"]["echo"] == "Hello E2E!"
        assert result_data["data"]["success"] is True

    finally:
        sandbox.stop()


@pytest.mark.e2e
def test_dev_discovery_with_failures() -> None:
    """Test that discovery handles failures gracefully and reports enhanced errors."""
    from huitzo_cli.sandbox.proxy import LocalSandbox

    # Start sandbox and discover
    sandbox = LocalSandbox()
    commands = sandbox.discover_commands()
    failures = sandbox.get_discovery_failures()

    # Should complete without crashing even if there are failures
    assert isinstance(commands, dict)
    assert isinstance(failures, list)

    # Test enhanced error classification
    error = ModuleNotFoundError("No module named 'test.commands'")
    classified = sandbox._classify_discovery_failure("test-pack", error)

    assert "test-pack" in classified
    assert "test.commands" in classified
