"""
Module: test_dashboard
Description: Tests for dashboard commands

Implements:
    - docs/cli/reference.md#dashboard-commands
    - docs/dashboards/manifest.md
    - docs/dashboards/publishing.md#build-contract
    - docs/api/rest.md#dashboards
"""

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

import yaml
from typer.testing import CliRunner

from huitzo_cli.commands.dashboard import dashboard_app


def _patch_auth():
    """Mock get_auth so dashboard new resolves namespace without a real backend."""
    mock_auth = MagicMock()
    mock_auth.get_current_user.return_value = {"tenantSlug": "testns"}
    return patch(
        "huitzo_cli.commands.dashboard.get_auth",
        return_value=(mock_auth, "test-token", "http://localhost:8000"),
    )


# --- dashboard new ---


def test_dashboard_new(temp_dir: Path) -> None:
    """Test creating new dashboard scaffolds all required files."""
    runner = CliRunner()

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

        with _patch_auth():
            result = runner.invoke(dashboard_app, ["new", "test-dashboard"], input="n\n")

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

        # Verify structure
        dashboard_dir = temp_dir / "test-dashboard"
        assert (dashboard_dir / "package.json").exists()
        assert (dashboard_dir / "vite.config.ts").exists()
        assert (dashboard_dir / "index.html").exists()
        assert (dashboard_dir / "huitzo-dashboard.yaml").exists()
        assert (dashboard_dir / "src" / "main.tsx").exists()
        assert (dashboard_dir / "src" / "dev.tsx").exists()
        assert (dashboard_dir / "src" / "App.tsx").exists()

        # Verify docs structure
        assert (dashboard_dir / "docs" / "README.md").exists()
        assert (dashboard_dir / "docs" / "pages" / "README.md").exists()
        assert (dashboard_dir / "docs" / "components" / "README.md").exists()
        assert (dashboard_dir / "docs" / "guides" / "README.md").exists()
        assert (dashboard_dir / "docs" / "guides" / "getting-started.md").exists()

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_module_contract(temp_dir: Path) -> None:
    """Test that scaffolded main.tsx exports mount and unmount."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with _patch_auth():
            runner.invoke(dashboard_app, ["new", "contract-test"], input="n\n")

        main_tsx = (temp_dir / "contract-test" / "src" / "main.tsx").read_text()
        assert "export function mount" in main_tsx
        assert "export function unmount" in main_tsx
        assert "HuitzoMountContext" in main_tsx
        assert "HuitzoProvider" in main_tsx

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_huitzo_context_matches_spec(temp_dir: Path) -> None:
    """Test that scaffolded dev context mock matches canonical spec from loading.md."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with _patch_auth():
            runner.invoke(dashboard_app, ["new", "context-test"], input="n\n")

        dev_tsx = (temp_dir / "context-test" / "src" / "dev.tsx").read_text()
        # Canonical fields from docs/dashboards/loading.md#huitzocontext
        # Now verified in dev.tsx mock context (main.tsx imports type from SDK)
        assert "navigate:" in dev_tsx
        assert "navigateToHub:" in dev_tsx
        assert "navigateToDashboard:" in dev_tsx
        assert "showNotification:" in dev_tsx
        assert "on:" in dev_tsx
        assert "emit:" in dev_tsx

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_dev_harness(temp_dir: Path) -> None:
    """Test that scaffolded dev.tsx imports mount from main."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with _patch_auth():
            runner.invoke(dashboard_app, ["new", "dev-test"], input="n\n")

        dev_tsx = (temp_dir / "dev-test" / "src" / "dev.tsx").read_text()
        assert "from './main'" in dev_tsx
        assert "mount(container, devContext)" in dev_tsx
        # Should not have unused createRoot import
        assert "import { createRoot }" not in dev_tsx
        # Dev harness should include simulated Hub header with theme toggle
        assert "createDevHeader" in dev_tsx

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_vite_library_mode(temp_dir: Path) -> None:
    """Test that vite.config.ts uses library mode for production builds."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with _patch_auth():
            runner.invoke(dashboard_app, ["new", "vite-test"], input="n\n")

        vite_config = (temp_dir / "vite-test" / "vite.config.ts").read_text()
        assert "lib:" in vite_config
        assert "entry:" in vite_config
        assert "'src/main.tsx'" in vite_config
        assert "formats: ['es']" in vite_config
        assert "cssCodeSplit: false" in vite_config

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_manifest(temp_dir: Path) -> None:
    """Test that huitzo-dashboard.yaml is generated with correct fields."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with _patch_auth():
            runner.invoke(dashboard_app, ["new", "manifest-test"], input="n\n")

        manifest_path = temp_dir / "manifest-test" / "huitzo-dashboard.yaml"
        manifest = yaml.safe_load(manifest_path.read_text())

        assert manifest["dashboard"]["name"] == "manifest-test"
        assert manifest["dashboard"]["version"] == "0.0.0"
        assert "namespace" in manifest["dashboard"]
        assert "description" in manifest["dashboard"]
        assert manifest["build"]["entry_point"] == "main.js"

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_index_html_references_dev(temp_dir: Path) -> None:
    """Test that index.html references dev.tsx, not main.tsx."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with _patch_auth():
            runner.invoke(dashboard_app, ["new", "html-test"], input="n\n")

        index_html = (temp_dir / "html-test" / "index.html").read_text()
        assert "src/dev.tsx" in index_html
        assert "src/main.tsx" not in index_html

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_hub_theme_tokens(temp_dir: Path) -> None:
    """Test that scaffolded index.html includes Hub design tokens and data-theme."""
    runner = CliRunner()

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)
        with _patch_auth():
            runner.invoke(dashboard_app, ["new", "theme-test"], input="n\n")

        index_html = (temp_dir / "theme-test" / "index.html").read_text()
        assert 'data-theme="dark"' in index_html
        assert "--color-bg-primary" in index_html
        assert '[data-theme="light"]' in index_html

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_exists(temp_dir: Path) -> None:
    """Test creating dashboard when directory exists."""
    runner = CliRunner()

    dashboard_dir = temp_dir / "existing"
    dashboard_dir.mkdir()

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

        result = runner.invoke(dashboard_app, ["new", "existing"])
        assert result.exit_code == 1
        assert "already exists" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_new_invalid_name(temp_dir: Path) -> None:
    """Test that invalid names are rejected before scaffolding."""
    runner = CliRunner()

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

        result = runner.invoke(dashboard_app, ["new", "InvalidName"])
        assert result.exit_code == 1
        assert "kebab-case" in result.stdout
        # Directory should NOT have been created
        assert not (temp_dir / "InvalidName").exists()

    finally:
        os.chdir(old_cwd)


# --- dashboard dev ---


def test_dashboard_dev(cli_runner: CliRunner) -> None:
    """Test dev command succeeds."""
    with patch("subprocess.run") as mock_run:
        mock_run.return_value.returncode = 0

        result = cli_runner.invoke(dashboard_app, ["dev"])
        assert result.exit_code == 0


def test_dashboard_dev_propagates_returncode(cli_runner: CliRunner) -> None:
    """Test dashboard dev propagates non-zero return code (W21)."""
    with patch("subprocess.run") as mock_run:
        mock_run.return_value.returncode = 1

        result = cli_runner.invoke(dashboard_app, ["dev"])
        assert result.exit_code == 1


# --- dashboard build ---


def test_dashboard_build(cli_runner: CliRunner) -> None:
    """Test build command."""
    with patch("subprocess.run") as mock_run:
        mock_run.return_value.returncode = 0

        result = cli_runner.invoke(dashboard_app, ["build"])
        assert result.exit_code == 0
        assert "Build successful" in result.stdout


# --- dashboard validate ---


def test_dashboard_validate_no_manifest(temp_dir: Path) -> None:
    """Test validate fails when no manifest exists."""
    runner = CliRunner()

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 1
        assert "not found" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_validate_empty_manifest(temp_dir: Path) -> None:
    """Test validate fails gracefully on empty manifest."""
    runner = CliRunner()

    (temp_dir / "huitzo-dashboard.yaml").write_text("")

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 1
        assert "empty" in result.stdout or "not a valid" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_validate_valid_manifest(temp_dir: Path) -> None:
    """Test validate passes with a valid manifest."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "test-dashboard",
            "namespace": "testdashboard",
            "version": "1.0.0",
            "description": "A test dashboard for validation",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    # Create dist/main.js with mount and unmount exports
    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 0
        assert "All checks passed" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_validate_invalid_name(temp_dir: Path) -> None:
    """Test validate catches invalid dashboard name."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "InvalidName",
            "namespace": "test",
            "version": "1.0.0",
            "description": "A test dashboard for validation",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 1
        assert "kebab-case" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_validate_invalid_version(temp_dir: Path) -> None:
    """Test validate catches invalid semver."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "test-dash",
            "namespace": "testdash",
            "version": "not-semver",
            "description": "A test dashboard for validation",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 1
        assert "semver" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_validate_invalid_namespace(temp_dir: Path) -> None:
    """Test validate catches invalid namespace format."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "test-dash",
            "namespace": "has-dashes",
            "version": "1.0.0",
            "description": "A test dashboard for validation",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 1
        assert "namespace" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_validate_short_description(temp_dir: Path) -> None:
    """Test validate catches description shorter than 10 characters."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "test-dash",
            "namespace": "testdash",
            "version": "1.0.0",
            "description": "Short",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 1
        assert "10 characters" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_validate_missing_unmount(temp_dir: Path) -> None:
    """Test validate catches bundle missing unmount export."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "test-dash",
            "namespace": "testdash",
            "version": "1.0.0",
            "description": "A test dashboard for validation",
        },
        "build": {"entry_point": "main.js", "output_directory": "dist"},
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    # Only mount, no unmount
    (dist_dir / "main.js").write_text("export function mount() {}")

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

        result = runner.invoke(dashboard_app, ["validate"])
        assert result.exit_code == 1
        assert "unmount" in result.stdout

    finally:
        os.chdir(old_cwd)


# --- dashboard publish ---


def test_dashboard_publish_no_manifest(temp_dir: Path) -> None:
    """Test publish fails without manifest."""
    runner = CliRunner()

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

        result = runner.invoke(dashboard_app, ["publish"])
        assert result.exit_code == 1
        assert "not found" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_dry_run(temp_dir: Path) -> None:
    """Test publish --dry-run creates tarball and shows contents."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "pub-test",
            "namespace": "pubtest",
            "version": "1.0.0",
            "description": "A test dashboard for publishing",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        result = runner.invoke(dashboard_app, ["publish", "--dry-run"])
        assert result.exit_code == 0
        assert "Dry run" in result.stdout
        assert "Tarball created" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_success(temp_dir: Path) -> None:
    """Test publish registers and uploads dashboard to backend."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "pub-test",
            "namespace": "pubtest",
            "version": "1.0.0",
            "description": "A test dashboard for publishing",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        mock_register = MagicMock()
        mock_register.status_code = 201
        mock_register.json.return_value = {"data": {"id": "dash-123"}}

        mock_upload = MagicMock()
        mock_upload.status_code = 201

        with (
            patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
            patch("huitzo_cli.commands.dashboard.httpx.post") as mock_post,
        ):
            mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
            mock_post.side_effect = [mock_register, mock_upload]

            result = runner.invoke(dashboard_app, ["publish"])

        assert result.exit_code == 0
        assert "Published" in result.stdout
        assert "pub-test" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_409_recovery(temp_dir: Path) -> None:
    """Test publish handles 409 (already exists) and looks up dashboard ID."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "pub-test",
            "namespace": "pubtest",
            "version": "1.0.0",
            "description": "A test dashboard for publishing",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        mock_register = MagicMock()
        mock_register.status_code = 409

        mock_lookup = MagicMock()
        mock_lookup.status_code = 200
        mock_lookup.json.return_value = {"data": {"id": "dash-456"}}

        mock_upload = MagicMock()
        mock_upload.status_code = 201

        with (
            patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
            patch("huitzo_cli.commands.dashboard.httpx.post") as mock_post,
            patch("huitzo_cli.commands.dashboard.httpx.get") as mock_get,
        ):
            mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
            mock_post.side_effect = [mock_register, mock_upload]
            mock_get.return_value = mock_lookup

            result = runner.invoke(dashboard_app, ["publish"])

        assert result.exit_code == 0
        assert "already registered" in result.stdout
        assert "Published" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_upload_failure(temp_dir: Path) -> None:
    """Test publish reports upload failure."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "pub-test",
            "namespace": "pubtest",
            "version": "1.0.0",
            "description": "A test dashboard for publishing",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        mock_register = MagicMock()
        mock_register.status_code = 201
        mock_register.json.return_value = {"data": {"id": "dash-123"}}

        mock_upload = MagicMock()
        mock_upload.status_code = 413
        mock_upload.json.return_value = {"error": {"message": "Bundle too large"}}

        with (
            patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
            patch("huitzo_cli.commands.dashboard.httpx.post") as mock_post,
        ):
            mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
            mock_post.side_effect = [mock_register, mock_upload]

            result = runner.invoke(dashboard_app, ["publish"])

        assert result.exit_code == 1
        assert "Upload failed" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_not_authenticated(temp_dir: Path) -> None:
    """Test publish fails when not authenticated."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "pub-test",
            "namespace": "pubtest",
            "version": "1.0.0",
            "description": "A test dashboard for publishing",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        with patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth:
            mock_auth.side_effect = SystemExit(1)

            result = runner.invoke(dashboard_app, ["publish"])

        assert result.exit_code == 1

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_network_error(temp_dir: Path) -> None:
    """Test publish handles network errors gracefully."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "pub-test",
            "namespace": "pubtest",
            "version": "1.0.0",
            "description": "A test dashboard for publishing",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        import httpx

        with (
            patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
            patch("huitzo_cli.commands.dashboard.httpx.post") as mock_post,
        ):
            mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
            mock_post.side_effect = httpx.ConnectError("Connection refused")

            result = runner.invoke(dashboard_app, ["publish"])

        assert result.exit_code == 1
        assert "Could not connect" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_malformed_register_response(temp_dir: Path) -> None:
    """Test publish handles 201 with missing data key gracefully."""
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "pub-test",
            "namespace": "pubtest",
            "version": "1.0.0",
            "description": "A test dashboard for publishing",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        mock_register = MagicMock()
        mock_register.status_code = 201
        mock_register.json.return_value = {}  # Missing "data" key

        mock_lookup = MagicMock()
        mock_lookup.status_code = 200
        mock_lookup.json.return_value = {"data": {"id": "dash-789"}}

        mock_upload = MagicMock()
        mock_upload.status_code = 201

        with (
            patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
            patch("huitzo_cli.commands.dashboard.httpx.post") as mock_post,
            patch("huitzo_cli.commands.dashboard.httpx.get") as mock_get,
        ):
            mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
            mock_post.side_effect = [mock_register, mock_upload]
            mock_get.return_value = mock_lookup

            result = runner.invoke(dashboard_app, ["publish"])

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

    finally:
        os.chdir(old_cwd)


# --- dashboard grant ---


VALID_TENANT_UUID = "550e8400-e29b-41d4-a716-446655440000"


def test_dashboard_grant_success(cli_runner: CliRunner) -> None:
    """Test grant command with mocked API responses."""
    mock_lookup = MagicMock()
    mock_lookup.status_code = 200
    mock_lookup.json.return_value = {"data": {"id": "dash-123"}}

    mock_grant = MagicMock()
    mock_grant.status_code = 201
    mock_grant.json.return_value = {"data": {"granted": 1, "dashboard_id": "dash-123"}}

    with (
        patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
        patch("huitzo_cli.commands.dashboard.httpx.get") as mock_get,
        patch("huitzo_cli.commands.dashboard.httpx.post") as mock_post,
    ):
        mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
        mock_get.return_value = mock_lookup
        mock_post.return_value = mock_grant

        result = cli_runner.invoke(dashboard_app, ["grant", "my-dashboard", VALID_TENANT_UUID])

    assert result.exit_code == 0
    assert "Granted access" in result.stdout


def test_dashboard_grant_invalid_uuid(cli_runner: CliRunner) -> None:
    """Test grant rejects invalid tenant UUID format."""
    result = cli_runner.invoke(dashboard_app, ["grant", "my-dashboard", "not-a-uuid"])
    assert result.exit_code == 1
    assert "Invalid tenant UUID" in result.stdout


def test_dashboard_grant_invalid_dashboard_name(cli_runner: CliRunner) -> None:
    """Test grant rejects invalid dashboard name format."""
    result = cli_runner.invoke(dashboard_app, ["grant", "InvalidName", VALID_TENANT_UUID])
    assert result.exit_code == 1
    assert "Invalid dashboard name" in result.stdout


def test_dashboard_grant_dashboard_not_found(cli_runner: CliRunner) -> None:
    """Test grant fails when dashboard is not found."""
    mock_lookup = MagicMock()
    mock_lookup.status_code = 404

    with (
        patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
        patch("huitzo_cli.commands.dashboard.httpx.get") as mock_get,
    ):
        mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
        mock_get.return_value = mock_lookup

        result = cli_runner.invoke(dashboard_app, ["grant", "nonexistent", VALID_TENANT_UUID])

    assert result.exit_code == 1
    assert "not found" in result.stdout


def test_dashboard_grant_failure(cli_runner: CliRunner) -> None:
    """Test grant reports backend errors."""
    mock_lookup = MagicMock()
    mock_lookup.status_code = 200
    mock_lookup.json.return_value = {"data": {"id": "dash-123"}}

    mock_grant = MagicMock()
    mock_grant.status_code = 400
    mock_grant.json.return_value = {
        "error": {"message": "Can only grant access to organization-visibility dashboards"}
    }

    with (
        patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
        patch("huitzo_cli.commands.dashboard.httpx.get") as mock_get,
        patch("huitzo_cli.commands.dashboard.httpx.post") as mock_post,
    ):
        mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
        mock_get.return_value = mock_lookup
        mock_post.return_value = mock_grant

        result = cli_runner.invoke(dashboard_app, ["grant", "my-dashboard", VALID_TENANT_UUID])

    assert result.exit_code == 1
    assert "Grant failed" in result.stdout


def test_dashboard_grant_network_error(cli_runner: CliRunner) -> None:
    """Test grant reports network errors distinctly from 'not found'."""
    import httpx

    with (
        patch("huitzo_cli.commands.dashboard.get_auth") as mock_auth,
        patch("huitzo_cli.commands.dashboard.httpx.get") as mock_get,
    ):
        mock_auth.return_value = (MagicMock(), "test-token", "http://localhost:8000")
        mock_get.side_effect = httpx.ConnectError("Connection refused")

        result = cli_runner.invoke(dashboard_app, ["grant", "my-dashboard", VALID_TENANT_UUID])

    assert result.exit_code == 1
    assert "Could not connect" in result.stdout


# --- dashboard revoke ---


def test_dashboard_revoke(cli_runner: CliRunner) -> None:
    """Test revoke shows not implemented."""
    result = cli_runner.invoke(dashboard_app, ["revoke", "my-dashboard", "tenant-uuid"])
    assert result.exit_code == 1
    assert "not yet implemented" in result.stdout


# --- dashboard share ---


def test_dashboard_share(cli_runner: CliRunner) -> None:
    """Test share shows not implemented."""
    result = cli_runner.invoke(dashboard_app, ["share", "my-dashboard"])
    assert result.exit_code == 1
    assert "not yet implemented" in result.stdout


# --- tarball structure ---


def test_dashboard_publish_tarball_has_no_dist_prefix(temp_dir: Path) -> None:
    """Tarball must contain main.js at root, not dist/main.js.

    The backend's extract_and_validate_bundle checks for main.js at the root
    of the extracted directory. If the CLI archives files relative to the project
    root (producing dist/main.js), the backend rejects the bundle.
    """
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "tar-test",
            "namespace": "tartest",
            "version": "1.0.0",
            "description": "A test dashboard for tarball structure",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")
    (dist_dir / "main.css").write_text(".app { color: red; }")

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

        result = runner.invoke(dashboard_app, ["publish", "--dry-run"])
        assert result.exit_code == 0

        # Verify tarball contents shown in output have no dist/ prefix
        assert "main.js" in result.stdout
        assert "main.css" in result.stdout
        assert "dist/main.js" not in result.stdout
        assert "dist/main.css" not in result.stdout

    finally:
        os.chdir(old_cwd)


def test_dashboard_publish_tarball_excludes_manifest(temp_dir: Path) -> None:
    """Tarball must NOT include huitzo-dashboard.yaml.

    The backend only allows specific file extensions (.js, .css, .json, etc.).
    Including the .yaml manifest causes a "Disallowed file type" rejection.
    """
    runner = CliRunner()

    manifest = {
        "dashboard": {
            "name": "tar-test",
            "namespace": "tartest",
            "version": "1.0.0",
            "description": "A test dashboard for tarball structure",
        },
        "build": {
            "entry_point": "main.js",
            "output_directory": "dist",
        },
    }
    (temp_dir / "huitzo-dashboard.yaml").write_text(yaml.dump(manifest))

    dist_dir = temp_dir / "dist"
    dist_dir.mkdir()
    (dist_dir / "main.js").write_text("export function mount() {} export function unmount() {}")

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

        result = runner.invoke(dashboard_app, ["publish", "--dry-run"])
        assert result.exit_code == 0

        # Manifest must NOT appear in bundle contents
        assert "huitzo-dashboard.yaml" not in result.stdout

    finally:
        os.chdir(old_cwd)
