"""
BLOOM LIMS GUI Endpoint Tests

Comprehensive pytest tests for all FastAPI GUI endpoints defined in main.py.
Tests verify endpoints return correct HTTP responses and HTML content.

With BLOOM_OAUTH=no, authentication is bypassed for testing.
"""

import os
import sys
import pytest
from unittest.mock import patch, MagicMock

# Set up auth bypass BEFORE importing FastAPI app
os.environ["BLOOM_OAUTH"] = "no"
os.environ["BLOOM_DEV_AUTH_BYPASS"] = "true"

from fastapi.testclient import TestClient

# Add root to path for main import
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from main import app


@pytest.fixture
def client():
    """Create test client with auth disabled."""
    return TestClient(app, raise_server_exceptions=False)


class TestPublicEndpoints:
    """Tests for public endpoints."""

    def test_root_index(self, client):
        """Test root index page returns HTML."""
        response = client.get("/")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_favicon(self, client):
        """Test favicon endpoint."""
        response = client.get("/favicon.ico")
        assert response.status_code in [200, 404]

    def test_login_page(self, client):
        """Test login page returns HTML."""
        response = client.get("/login")
        assert response.status_code in [200, 401]

    def test_help_page(self, client):
        """Test help page returns HTML."""
        response = client.get("/help")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]


class TestMainGUIEndpoints:
    """Tests for main GUI endpoints (auth bypassed)."""

    def test_index2_returns_html(self, client):
        """Test index2 page returns HTML."""
        response = client.get("/index2")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_lims_returns_html(self, client):
        """Test LIMS page returns HTML."""
        response = client.get("/lims")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_admin_returns_html(self, client):
        """Test admin page returns HTML."""
        response = client.get("/admin")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_admin_includes_tapdb_metrics_link(self, client):
        """Admin page includes link to TapDB metrics."""
        response = client.get("/admin")
        assert response.status_code == 200
        assert "/admin/metrics" in response.text

    def test_admin_metrics_returns_html(self, client):
        """TapDB metrics page returns HTML."""
        response = client.get("/admin/metrics")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]
        assert "TapDB Metrics" in response.text

    def test_admin_preferences_post_redirects(self, client):
        """Test admin preferences form POST succeeds and redirects."""
        response = client.post(
            "/admin",
            data={
                "print_lab": "BLOOM",
                "printer_name": "test-printer",
                "label_zpl_style": "tube_2inX1in",
                "style_css": "/static/modern/css/bloom_modern.css",
            },
            follow_redirects=False,
        )
        assert response.status_code == 303
        assert response.headers.get("location") == "/admin?saved=1"

    def test_admin_saved_banner_renders(self, client):
        """Test saved confirmation banner appears on admin page."""
        response = client.get("/admin?saved=1")
        assert response.status_code == 200
        assert "Preferences saved." in response.text

    def test_admin_start_zebra_service_redirects_on_success(self, client):
        """Test zebra start action launches command and redirects with success flag."""
        with patch("bloom_lims.gui.routes.operations._zebra_command_status", return_value=("stopped", "stopped")):
            with patch(
                "bloom_lims.gui.routes.operations._try_start_zebra_background",
                return_value=("started", "ok"),
            ):
                response = client.post("/admin/zebra/start", follow_redirects=False)

        assert response.status_code == 303
        assert response.headers.get("location") == "/admin?zebra_started=1"

    def test_update_preference_persists_display_timezone(self, client):
        """Display timezone preference writes through shared preference persistence."""
        with patch(
            "bloom_lims.gui.routes.operations.persist_display_timezone",
            return_value=True,
        ) as mocked_persist:
            response = client.post(
                "/update_preference",
                json={"key": "display_timezone", "value": "America/Chicago"},
            )

        assert response.status_code == 200
        assert response.json()["status"] == "success"
        mocked_persist.assert_called_once_with(
            "john@daylilyinformatics.com",
            "America/Chicago",
        )


class TestAssaysEndpoint:
    """Tests for retired /assays endpoint."""

    def test_assays_default_returns_html(self, client):
        """Assay workflow pages are unmounted in queue-centric Bloom beta."""
        response = client.get("/assays")
        assert response.status_code == 404

    def test_assays_show_type_all(self, client):
        """Unmounted route stays unavailable regardless of query params."""
        response = client.get("/assays?show_type=all")
        assert response.status_code == 404

    def test_assays_show_type_accessioning(self, client):
        """Accessioning query still resolves to unmounted route."""
        response = client.get("/assays?show_type=accessioning")
        assert response.status_code == 404

    def test_assays_show_type_assay(self, client):
        """Assay-specific query still resolves to unmounted route."""
        response = client.get("/assays?show_type=assay")
        assert response.status_code == 404

    def test_admin_has_api_token_management_panels(self, client):
        """Admin page renders API token user and token-management panels."""
        response = client.get("/admin")
        assert response.status_code == 200
        assert "API Token Administration" in response.text
        assert "API Access Users" in response.text
        assert "Issued Tokens" in response.text

    def test_admin_has_atlas_and_ursa_enablement_panels(self, client):
        """Admin page renders Atlas/Ursa API enablement controls."""
        response = client.get("/admin")
        assert response.status_code == 200
        assert "Enable Atlas API" in response.text
        assert "Enable Ursa API" in response.text

    def test_admin_has_group_membership_management_panel(self, client):
        """Admin page renders generic group assignment/removal controls."""
        response = client.get("/admin")
        assert response.status_code == 200
        assert "Group Membership Management" in response.text
        assert "Add User To Selected Group" in response.text


class TestEuidDetailsEndpoint:
    """Tests for /euid_details endpoint."""

    def test_euid_details_requires_euid(self, client):
        """Test euid_details requires euid parameter."""
        response = client.get("/euid_details")
        assert response.status_code == 422  # Validation error

    def test_euid_details_with_nonexistent_euid(self, client):
        """Test euid_details with non-existent euid returns 404."""
        response = client.get("/euid_details?euid=NONEXISTENT_TEST_EUID")
        assert response.status_code == 404


class TestModernActionUI:
    """Tests for modern UI action wiring on object detail pages."""

    def test_euid_details_uses_modern_action_module(self, client, bdb):
        GI = bdb.Base.classes.generic_instance
        instance = bdb.session.query(GI).filter(GI.is_deleted.is_(False)).first()
        assert instance is not None

        response = client.get(f"/euid_details?euid={instance.euid}")
        assert response.status_code == 200
        assert "/static/modern/js/action_buttons.js" in response.text

    def test_ui_actions_execute_endpoint_invokes_modern_service(self, client):
        payload = {
            "euid": "OB_TEST_123",
            "action_group": "state",
            "action_key": "/state/set-object-status/",
            "captured_data": {"status": "ready"},
        }
        with patch(
            "bloom_lims.gui.routes.operations.execute_action_for_instance",
            return_value={"status": "success", "message": "ok"},
        ) as mocked_execute:
            response = client.post("/ui/actions/execute", json=payload)

        assert response.status_code == 200
        assert response.json()["status"] == "success"
        mocked_execute.assert_called_once()


@pytest.mark.skip(reason="Workflow GUI routes are retired in queue-centric Bloom beta.")
@pytest.mark.skip(reason="Workflow GUI routes are retired in queue-centric Bloom beta.")
@pytest.mark.skip(reason="Workflow GUI routes are retired in queue-centric Bloom beta.")
class TestWorkflowEndpoints:
    """Tests for workflow-related endpoints."""

    def test_workflow_summary_returns_html(self, client):
        """Test workflow summary returns HTML."""
        response = client.get("/workflow_summary")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_workflow_details_requires_euid(self, client):
        """Test workflow details requires euid parameter."""
        response = client.get("/workflow_details")
        assert response.status_code == 422

class TestEquipmentEndpoints:
    """Tests for equipment-related endpoints."""

    def test_equipment_overview_returns_html(self, client):
        """Test equipment overview returns HTML."""
        response = client.get("/equipment_overview")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_create_from_template_get_returns_html(self, client):
        """Test create from template GET returns HTML."""
        response = client.get("/create_from_template")
        assert response.status_code in [200, 400]
        assert "text/html" in response.headers["content-type"]


class TestReagentControlEndpoints:
    """Tests for reagent and control endpoints."""

    def test_reagent_overview_returns_html(self, client):
        """Test reagent overview returns HTML."""
        response = client.get("/reagent_overview")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_control_overview_returns_html(self, client):
        """Legacy control overview page is retired."""
        response = client.get("/control_overview")
        assert response.status_code == 404


class TestTemplateEndpoints:
    """Tests for template-related endpoints."""

    def test_object_templates_summary_returns_html(self, client):
        """Test object templates summary returns HTML."""
        response = client.get("/object_templates_summary")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]


class TestPlateEndpoints:
    """Tests for plate visualization endpoints."""

    def test_plate_visualization_requires_euid(self, client):
        """Test plate visualization requires euid parameter."""
        response = client.get("/plate_visualization")
        assert response.status_code == 422

    def test_plate_carosel2_requires_euid(self, client):
        """Test plate carousel requires euid parameter."""
        response = client.get("/plate_carosel2")
        assert response.status_code == 422

    def test_get_related_plates_requires_euid(self, client):
        """Test get related plates requires euid parameter."""
        response = client.get("/get_related_plates")
        assert response.status_code == 422


class TestDAGEndpoints:
    """Tests for DAG (Directed Acyclic Graph) visualization endpoints."""

    def test_dagg_requires_euid(self, client):
        """Test DAG visualization requires euid parameter."""
        response = client.get("/dagg")
        assert response.status_code in [200, 422, 500]

    def test_dindex2_returns_html(self, client):
        """Test DAG index returns HTML."""
        response = client.get("/dindex2")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_get_dagv2_requires_euid(self, client):
        """Test get_dagv2 requires euid parameter."""
        response = client.get("/get_dagv2")
        assert response.status_code in [422, 500]


class TestFileEndpoints:
    """Tests for file management endpoints."""

    def test_dewey_returns_html(self, client):
        """Legacy Dewey owner page is removed from Bloom."""
        response = client.get("/dewey")
        assert response.status_code == 404

    def test_bulk_create_files_returns_html(self, client):
        """Legacy bulk create files page is retired."""
        response = client.get("/bulk_create_files")
        assert response.status_code in [404, 410]


class TestDatabaseEndpoints:
    """Tests for database information endpoints."""

    def test_database_statistics_returns_html(self, client):
        """Test database statistics returns HTML (fixed: was failing with await on sync query)."""
        response = client.get("/database_statistics")
        assert response.status_code == 200, f"Expected 200, got {response.status_code}"
        assert "text/html" in response.headers["content-type"]

    def test_database_statistics_contains_stats_sections(self, client):
        """Test database statistics page contains expected stats sections."""
        response = client.get("/database_statistics")
        assert response.status_code == 200
        # Page should contain statistics sections
        content = response.text
        assert "statistics" in content.lower() or "stats" in content.lower()

    def test_bloom_schema_report_returns_html(self, client):
        """Legacy schema report page is retired."""
        response = client.get("/bloom_schema_report")
        assert response.status_code == 410
        assert "application/json" in response.headers["content-type"]


class TestUserEndpoints:
    """Tests for user-related endpoints."""

    def test_user_home_returns_html(self, client):
        """Test user home page returns HTML (fixed: was accessing session before auth check)."""
        response = client.get("/user_home")
        assert response.status_code == 200, f"Expected 200, got {response.status_code}"
        assert "text/html" in response.headers["content-type"]
        assert "bloom_modern.css" in response.text

    def test_user_home_or_redirects_to_login(self, client):
        """Test user home page returns content or redirects to login (based on auth state)."""
        response = client.get("/user_home", follow_redirects=False)
        # Should either return 200 (authenticated) or 307 redirect to login
        assert response.status_code in [200, 307], f"Expected 200 or 307, got {response.status_code}"
        if response.status_code == 307:
            assert "/login" in response.headers.get("location", "")

    def test_user_audit_logs_returns_html(self, client):
        """Test user audit logs returns HTML."""
        response = client.get("/user_audit_logs")
        assert response.status_code in [200, 422, 500]
        if response.status_code == 200:
            assert "text/html" in response.headers["content-type"]


class TestQueueEndpoints:
    """Tests for queue management endpoints."""

    def test_queue_details_returns_html(self, client):
        """Test queue details returns HTML."""
        response = client.get("/queue_details")
        assert response.status_code in [200, 500]
        if response.status_code == 200:
            assert "text/html" in response.headers["content-type"]

    def test_set_filter(self, client):
        """Test set filter endpoint works."""
        response = client.get("/set_filter")
        # May redirect or return success
        assert response.status_code in [200, 302, 303]


class TestDeleteAliasEndpoints:
    """Tests for delete/undelete alias endpoints."""

    def test_delete_by_euid_requires_euid(self, client):
        """Test delete by EUID requires euid parameter."""
        response = client.get("/delete_by_euid")
        assert response.status_code == 422

    def test_un_delete_by_uuid_route_removed(self, client):
        """Legacy undelete alias route must be retired."""
        response = client.get("/un_delete_by_uuid")
        assert response.status_code == 404

    def test_un_delete_by_uuid_uuid_lookup_route_removed(self, client):
        """Legacy undelete alias route must stay unavailable even for UUID queries."""
        response = client.get("/un_delete_by_uuid?uuid=invalid-uuid")
        assert response.status_code == 404


class TestScriptsEndpoint:
    """Tests for scripts listing endpoint."""

    def test_list_scripts_returns_json(self, client):
        """Test list-scripts returns JSON response."""
        response = client.get("/list-scripts")
        assert response.status_code in [200, 500]
        if response.status_code == 200:
            assert "application/json" in response.headers["content-type"]


class TestLegacyRoutes:
    """Tests for removed legacy UI routes."""

    def test_legacy_index_removed(self, client):
        """Legacy index route should be removed."""
        response = client.get("/legacy/")
        assert response.status_code == 404

    def test_legacy_login_removed(self, client):
        """Legacy login route should be removed."""
        response = client.get("/legacy/login")
        assert response.status_code == 404


class TestModernUIRoutes:
    """Tests for modern UI routes (GUI modernization)."""

    def test_modern_dashboard(self, client):
        """Test modern dashboard at root renders correctly."""
        response = client.get("/")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]
        # Check for modern UI indicators
        content = response.text
        assert "BLOOM" in content

    def test_search_page_renders(self, client):
        """Test search page renders correctly."""
        response = client.get("/search")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]
        content = response.text
        assert "Search" in content

    def test_search_page_with_query(self, client):
        """Test search page with query parameter."""
        response = client.get("/search?q=test")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_search_page_with_type_filter(self, client):
        """Test search page with type filter."""
        response = client.get("/search?q=test&types=container,content")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_search_page_includes_sortable_headers_script(self, client):
        """Test search page includes client-side sortable table behavior."""
        response = client.get("/search")
        assert response.status_code == 200
        content = response.text
        assert "enableSearchResultSorting" in content
        assert "sortable-col" in content

    def test_bulk_create_containers_page_renders(self, client):
        """Test bulk create containers page renders correctly."""
        response = client.get("/bulk_create_containers")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]
        content = response.text
        assert "Bulk Create" in content
        assert "TSV" in content


class TestModernTemplateUsage:
    """Tests to verify endpoints use modern templates correctly."""

    def test_dashboard_uses_modern_css(self, client):
        """Test dashboard uses modern CSS."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content

    def test_assays_uses_modern_template(self, client):
        """Assays route is removed (not templated, not stubbed)."""
        response = client.get("/assays")
        assert response.status_code == 404

    def test_workflow_summary_uses_modern_template(self, client):
        """Workflow summary route is retired."""
        response = client.get("/workflow_summary")
        assert response.status_code == 404

    def test_equipment_overview_uses_modern_template(self, client):
        """Test equipment overview uses modern template."""
        response = client.get("/equipment_overview")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content
        assert "Equipment" in content

    def test_reagent_overview_uses_modern_template(self, client):
        """Test reagent overview uses modern template."""
        response = client.get("/reagent_overview")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content
        assert "Reagent" in content

    def test_admin_uses_modern_template(self, client):
        """Test admin page uses modern template."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content
        assert "Admin" in content

    def test_login_uses_modern_template(self, client):
        """Test login page uses modern template."""
        response = client.get("/login")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content

    def test_search_uses_modern_template(self, client):
        """Test search page uses modern template."""
        response = client.get("/search")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content
        assert "Search" in content

    def test_bulk_create_uses_modern_template(self, client):
        """Test bulk create containers uses modern template."""
        response = client.get("/bulk_create_containers")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content


class TestModernUIElements:
    """Tests to verify modern UI elements are present."""

    def test_dashboard_has_stat_cards(self, client):
        """Test dashboard has stat cards."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text
        assert "stat-card" in content or "card" in content

    def test_navigation_has_modern_elements(self, client):
        """Test navigation has modern elements."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text
        # Check for Font Awesome icons
        assert "fa-" in content
        # Check for navigation links
        assert "nav" in content.lower()

    def test_footer_has_version(self, client):
        """Test footer has version info."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text
        assert "footer" in content.lower()

    def test_pages_have_breadcrumb_or_header(self, client):
        """Queue runtime remains visible from the dashboard after assay-page retirement."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text
        assert "page-header" in content or "Queue Runtime" in content


class TestModernAPIs:
    """Tests for modern API endpoints."""

    def test_dashboard_stats_api(self, client):
        """Test dashboard stats API endpoint."""
        response = client.get("/api/v1/stats/dashboard")
        assert response.status_code == 200
        assert "application/json" in response.headers["content-type"]
        data = response.json()
        assert "containers" in data or "total" in str(data).lower()

    def test_search_api_endpoint(self, client):
        """Test search API returns JSON."""
        response = client.get("/api/v1/search?q=test&format=json")
        # May return 200 with results or appropriate status
        assert response.status_code in [200, 404, 422]

    def test_bulk_create_api_post(self, client):
        """Test bulk create API accepts POST."""
        # Test with invalid data - should return validation error
        response = client.post(
            "/api/v1/containers/bulk-create",
            json={"containers": []}
        )
        # Should return 200 or validation error, not 500
        assert response.status_code in [200, 201, 400, 422]


class TestAdminDependencyInfo:
    """Tests for admin page dependency information display."""

    def test_admin_shows_external_integrations(self, client):
        """Test admin page shows External Integrations section."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "External Integrations" in content

    def test_admin_shows_zebra_day(self, client):
        """Test admin page shows zebra_day integration."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "zebra_day" in content
        assert "Zebra printer" in content.lower() or "printer" in content.lower()
        assert "https://localhost:8118/" in content

    def test_admin_shows_carrier_tracking(self, client):
        """Test admin page shows carrier tracking integration."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "carrier-tracking" in content or "carrier_tracking" in content

    def test_admin_shows_tapdb(self, client):
        """Test admin page shows daylily-tapdb integration."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "tapdb" in content.lower()

    def test_admin_shows_cognito(self, client):
        """Test admin page shows Cognito auth info."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "Cognito" in content

    def test_admin_shows_bloom_version(self, client):
        """Test admin page shows BLOOM version."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "BLOOM Version" in content or "bloom_version" in content.lower()

    def test_admin_shows_atlas_webhook_secret_panel(self, client):
        """Test admin page shows Atlas webhook secret panel."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert "Atlas Webhook Signature Secret" in content
        assert "X-Bloom-Signature" in content

    def test_admin_shows_atlas_webhook_secret_controls(self, client):
        """Test admin page renders view/copy/hide controls for webhook secret."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text
        assert 'id="atlas-webhook-secret-value"' in content
        assert 'id="atlas-webhook-secret-view"' in content
        assert 'id="atlas-webhook-secret-copy"' in content
        assert 'id="atlas-webhook-secret-hide"' in content


class TestObjectCreationWizard:
    """Tests for object creation wizard functionality."""

    def test_create_object_page_returns_html(self, client):
        """Test create object page returns HTML."""
        response = client.get("/create_object")
        assert response.status_code == 200
        assert "text/html" in response.headers["content-type"]

    def test_create_object_page_uses_modern_template(self, client):
        """Test create object page uses modern template."""
        response = client.get("/create_object")
        assert response.status_code == 200
        content = response.text
        assert "bloom_modern.css" in content
        assert "Create Object" in content

    def test_create_object_page_has_wizard_steps(self, client):
        """Test create object page has wizard steps."""
        response = client.get("/create_object")
        assert response.status_code == 200
        content = response.text
        assert "Super Type" in content
        assert "wizard-step" in content

    def test_create_object_page_has_assay_shortcut(self, client):
        """Create page must not expose retired workflow-assay shortcut."""
        response = client.get("/create_object")
        assert response.status_code == 200
        content = response.text
        assert "Shortcut: workflow / assay" not in content
        assert "selectAssayShortcut()" not in content

    def test_create_object_navigation_link(self, client):
        """Test create object link is in navigation."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text
        assert "/create_object" in content or "Create" in content


class TestObjectCreationAPI:
    """Tests for object creation API endpoints."""

    def test_categories_api(self, client):
        """Test categories API endpoint."""
        response = client.get("/api/v1/object-creation/categories")
        assert response.status_code == 200
        data = response.json()
        assert "categories" in data
        assert len(data["categories"]) > 0
        # Should include common categories
        names = [st["name"] for st in data["categories"]]
        assert "container" in names
        assert "content" in names

    def test_categories_api_excludes_retired_domains(self, client):
        """Retired workflow/test requisition categories must be absent."""
        response = client.get("/api/v1/object-creation/categories")
        assert response.status_code == 200
        names = [st.get("name") for st in response.json().get("categories", [])]
        assert "workflow" not in names
        assert "workflow_step" not in names
        assert "test_requisition" not in names

    def test_categories_api_is_alpha_sorted(self, client):
        """Categories should be case-insensitive alpha sorted."""
        response = client.get("/api/v1/object-creation/categories")
        assert response.status_code == 200
        names = [st.get("display_name", st.get("name", "")) for st in response.json().get("categories", [])]
        assert names == sorted(names, key=lambda value: str(value).casefold())

    def test_types_api(self, client):
        """Test types API endpoint for container category."""
        response = client.get("/api/v1/object-creation/types?category=container")
        assert response.status_code == 200
        data = response.json()
        assert "types" in data
        assert data["category"] == "container"
        assert len(data["types"]) > 0
        # Should include common container types
        names = [t["name"] for t in data["types"]]
        assert "tube" in names or "plate" in names

    def test_types_api_invalid_category(self, client):
        """Test types API returns 404 for invalid category."""
        response = client.get("/api/v1/object-creation/types?category=nonexistent")
        assert response.status_code == 404

    def test_subtypes_api(self, client):
        """Test subtypes API endpoint."""
        response = client.get("/api/v1/object-creation/subtypes?category=container&type=tube")
        assert response.status_code == 200
        data = response.json()
        assert "subtypes" in data
        assert data["category"] == "container"
        assert data["type"] == "tube"
        assert len(data["subtypes"]) > 0
        # Each subtype should have versions
        for st in data["subtypes"]:
            assert "versions" in st
            assert len(st["versions"]) > 0

    def test_template_api(self, client):
        """Test template details API endpoint."""
        # First get a subtype
        response = client.get("/api/v1/object-creation/subtypes?category=container&type=tube")
        assert response.status_code == 200
        subtypes = response.json()["subtypes"]
        if subtypes:
            subtype = subtypes[0]["name"]
            version = subtypes[0]["versions"][0]

            # Get template details
            response = client.get(
                f"/api/v1/object-creation/template?category=container&type=tube&subtype={subtype}&version={version}"
            )
            assert response.status_code == 200
            data = response.json()
            assert "template" in data
            assert data["category"] == "container"
            assert data["type"] == "tube"
            assert data["subtype"] == subtype


class TestLoginLogoutButtonDisplay:
    """Tests for login/logout button display logic in modern UI header.

    The base template checks for either 'user' or 'udat' variables to determine
    whether to show the logout button (authenticated) or login button (unauthenticated).
    """

    def test_authenticated_user_sees_logout_button(self, client):
        """Test that authenticated users see the logout button and their email."""
        # With BLOOM_OAUTH=no, the session is populated with test user data
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        # Should see logout button
        assert "Logout" in content or "logout" in content.lower()
        # Should see the user email (from test session)
        assert "john@daylilyinformatics.com" in content or "email" in content.lower()
        assert 'href="/user_home"' in content

    def test_dashboard_shows_logout_with_udat(self, client):
        """Test dashboard page shows logout button when udat is passed."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        # The dashboard passes 'udat' to the template
        # Should show logout, not login
        assert 'href="/logout"' in content
        assert 'fa-sign-out-alt' in content  # Logout icon

    def test_create_object_shows_logout_with_user(self, client):
        """Test create_object page shows logout button when user is passed."""
        response = client.get("/create_object")
        assert response.status_code == 200
        content = response.text

        # The create_object route passes both 'user' and 'user_data'
        # Should show logout, not login
        assert 'href="/logout"' in content

    def test_search_page_shows_logout(self, client):
        """Test search page shows logout button for authenticated users."""
        response = client.get("/search")
        assert response.status_code == 200
        content = response.text

        # Should show logout button
        assert 'href="/logout"' in content

    def test_admin_page_shows_logout(self, client):
        """Test admin page shows logout button for authenticated users."""
        response = client.get("/admin")
        assert response.status_code == 200
        content = response.text

        # Should show logout button
        assert 'href="/logout"' in content

    def test_assays_page_shows_logout(self, client):
        """Unmounted assays page no longer renders authenticated HTML."""
        response = client.get("/assays")
        assert response.status_code == 404


class TestModernUINavigation:
    """Tests for modern UI navigation elements."""

    def test_dashboard_has_navigation_links(self, client):
        """Test dashboard has all expected navigation links."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        # Check for main navigation links
        assert 'href="/"' in content  # Dashboard
        assert 'href="/queue_details"' in content
        assert 'href="/assays"' not in content
        assert 'href="/workflows"' not in content
        assert 'href="/admin"' in content
        assert 'href="/create_object"' in content
        assert 'href="/search"' in content

    def test_dashboard_hides_admin_nav_for_non_admin(self, client):
        from fastapi import Request
        from bloom_lims.gui.deps import require_auth

        async def _non_admin_auth(request: Request):
            request.session["user_data"] = {
                "email": "non-admin@example.com",
                "role": "user",
                "dag_fnv2": "",
            }
            return request.session["user_data"]

        app.dependency_overrides[require_auth] = _non_admin_auth
        try:
            response = client.get("/")
            assert response.status_code == 200
            assert 'href="/admin"' not in response.text
        finally:
            app.dependency_overrides.pop(require_auth, None)

    def test_dashboard_has_no_legacy_link(self, client):
        """Test dashboard does not expose legacy UI link."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        assert 'href="/legacy/' not in content

    def test_modern_pages_use_bloom_modern_css(self, client):
        """Test modern pages include the modern CSS file."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        # Should include modern CSS
        assert "bloom_modern.css" in content

    def test_modern_pages_use_bloom_modern_js(self, client):
        """Test modern pages include the modern JS file."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        # Should include modern JS
        assert "bloom_modern.js" in content

    def test_footer_has_help_support_and_repo_links(self, client):
        """Test footer includes help, support email, and GitHub links."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        assert 'href="/help"' in content
        assert "mailto:" in content
        assert "github.com/Daylily-Informatics/bloom" in content

    def test_footer_has_git_metadata(self, client):
        """Test footer includes git metadata labels."""
        response = client.get("/")
        assert response.status_code == 200
        content = response.text

        assert "Branch:" in content
        assert "Tag:" in content
        assert "Commit:" in content


class TestFileAndDeweyEndpoints:
    """Tests for file management (Dewey) endpoints."""

    def test_visual_report_returns_html(self, client):
        """Test visual report page returns HTML or handles gracefully."""
        response = client.get("/visual_report")
        # May fail due to missing file, accept various responses
        assert response.status_code in [307, 400, 404, 410, 500]

    def test_search_files_post(self, client):
        """Test search files POST endpoint."""
        response = client.post("/search_files", data={"search_query": "test"})
        assert response.status_code in [404, 410, 422, 307]

    def test_search_file_sets_post(self, client):
        """Test search file sets POST endpoint."""
        response = client.post("/search_file_sets", data={"search_query": "test"})
        assert response.status_code in [404, 410, 422, 307]

    def test_file_set_urls_requires_euid(self, client):
        """Test file_set_urls endpoint requires EUID parameter."""
        response = client.get("/file_set_urls")
        # Should handle missing parameter gracefully
        assert response.status_code in [404, 307, 400, 422, 500]

    def test_delete_temp_file(self, client):
        """Test delete temp file endpoint."""
        response = client.get("/delete_temp_file")
        # Should handle missing file gracefully
        assert response.status_code in [404, 307, 400, 422]


class TestGraphAndVisualizationEndpoints:
    """Tests for graph and visualization endpoints."""

    def test_get_dagv2_requires_euid(self, client):
        """Test get_dagv2 endpoint."""
        response = client.get("/get_dagv2")
        # Missing euid should be handled
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_get_node_info_requires_node(self, client):
        """Test get_node_info endpoint."""
        response = client.get("/get_node_info")
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_get_node_property_requires_euid(self, client):
        """Test get_node_property endpoint."""
        response = client.get("/get_node_property")
        assert response.status_code in [200, 307, 400, 422, 500]


class TestDataModificationEndpoints:
    """Tests for data modification POST endpoints."""

    def test_query_by_euids_post(self, client):
        """Test query by EUIDs POST endpoint."""
        response = client.post("/query_by_euids", data={"euids": "EX1"})
        assert response.status_code in [200, 307, 422]

    def test_update_preference_post(self, client):
        """Test update preference POST endpoint."""
        response = client.post(
            "/update_preference",
            json={"preference_key": "skin_css", "preference_value": "/static/modern/css/bloom_modern.css"},
        )
        assert response.status_code in [200, 307, 422]

    def test_save_json_addl_key_post(self, client):
        """Test save JSON addl key POST endpoint."""
        response = client.post(
            "/save_json_addl_key",
            json={"euid": "EX1", "json_addl_key": "test", "json_data": {"value": "test"}},
        )
        assert response.status_code in [200, 307, 400, 404, 422, 500]

    def test_update_accordion_state_post(self, client):
        """Test update accordion state POST endpoint."""
        response = client.post(
            "/update_accordion_state",
            json={"step_euid": "WF1", "section": "test", "is_open": True, "state": "open"},
        )
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_generic_templates_post(self, client):
        """Test generic templates POST endpoint."""
        response = client.post("/generic_templates", data={"query": "test"})
        assert response.status_code in [200, 307, 400, 422, 500]


class TestAdminAndConfigEndpoints:
    """Tests for admin and configuration endpoints."""

    def test_admin_template_get(self, client):
        """Test admin template GET endpoint."""
        response = client.get("/admin_template")
        assert response.status_code in [404, 307, 400, 410, 422]

    def test_update_object_name_requires_params(self, client):
        """Test update object name endpoint requires parameters."""
        response = client.get("/update_object_name")
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_uuid_details_route_removed(self, client):
        """Legacy UUID details alias route must be retired."""
        response = client.get("/uuid_details")
        assert response.status_code == 404

    def test_uuid_details_uuid_lookup_route_removed(self, client):
        """Legacy UUID details alias route must stay unavailable for UUID queries."""
        response = client.get("/uuid_details?uuid=invalid-uuid")
        assert response.status_code == 404

    def test_vertical_exp_requires_euid(self, client):
        """Legacy vertical experiment endpoint is retired."""
        response = client.get("/vertical_exp?euid=EX1")
        assert response.status_code == 404


class TestCalculationEndpoints:
    """Tests for calculation endpoints."""

    def test_calculate_cogs_parents(self, client):
        """Test calculate COGS parents endpoint."""
        response = client.get("/calculate_cogs_parents")
        # Should handle missing parameters
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_calculate_cogs_children(self, client):
        """Test calculate COGS children endpoint."""
        response = client.get("/calculate_cogs_children")
        # Should handle missing parameters
        assert response.status_code in [200, 307, 400, 422, 500]


class TestProtectedEndpoints:
    """Tests for protected content endpoints."""

    def test_protected_content(self, client):
        """Test protected content endpoint."""
        response = client.get("/protected_content")
        assert response.status_code in [404, 200, 307, 400, 401, 403]

    def test_serve_endpoint_with_path(self, client):
        """Test serve endpoint with file path."""
        response = client.get("/serve_endpoint/test.txt")
        assert response.status_code in [200, 307, 400, 404]


@pytest.mark.skip(reason="Workflow GUI routes are retired in queue-centric Bloom beta.")
class TestWorkflowEndpoints:
    """Tests for workflow-related endpoints."""

    def test_legacy_step_action_route_removed(self, client):
        """Legacy workflow step action endpoint must be removed."""
        legacy_path = "/workflow_step" + "_action"
        response = client.post(
            legacy_path,
            json={"step_euid": "WF1", "action": "complete"},
        )
        assert response.status_code == 404

    def test_update_obj_json_addl_properties_post(self, client):
        """Test update object JSON addl properties POST endpoint."""
        response = client.post(
            "/update_obj_json_addl_properties",
            data={"obj_euid": "EX1", "json_note": "test"},
        )
        assert response.status_code in [200, 307, 400, 404, 422, 500]


class TestDeleteEndpoints:
    """Tests for delete-related endpoints."""

    def test_delete_object_post(self, client):
        """Test delete object POST endpoint."""
        response = client.post(
            "/delete_object",
            json={"euid": "EX1"},
        )
        assert response.status_code in [200, 307, 400, 404, 422, 500]


class TestDAGEndpoints:
    """Tests for DAG (directed acyclic graph) endpoints."""

    def test_update_dag_post(self, client):
        """Test update DAG POST endpoint."""
        response = client.post(
            "/update_dag",
            json={"euid": "EX1", "nodes": [], "edges": []},
        )
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_update_dag_post_writes_under_dags(self, client, monkeypatch, tmp_path):
        """Test update DAG writes files under dags/ and not repo root."""
        monkeypatch.chdir(tmp_path)
        response = client.post(
            "/update_dag",
            json={"euid": "EX1", "nodes": [], "edges": []},
        )

        assert response.status_code == 200
        payload = response.json()
        assert payload["status"] == "success"
        assert payload["filename"].startswith("dags/dag_")
        assert (tmp_path / payload["filename"]).exists()
        assert not list(tmp_path.glob("dag_*"))

    def test_add_new_edge_post(self, client):
        """Test add new edge POST endpoint."""
        response = client.post(
            "/add_new_edge",
            json={"parent_euid": "EX1", "child_euid": "EX2"},
        )
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_delete_node_post(self, client):
        """Test delete node POST endpoint."""
        response = client.post(
            "/delete_node",
            json={"euid": "EX1"},
        )
        assert response.status_code in [200, 307, 400, 422, 500]

    def test_delete_edge_post(self, client):
        """Test delete edge POST endpoint."""
        response = client.post(
            "/delete_edge",
            json={"parent_euid": "EX1", "child_euid": "EX2"},
        )
        assert response.status_code in [200, 307, 400, 422, 500]


class TestFileCreationEndpoints:
    """Tests for file creation endpoints."""

    def test_create_file_post(self, client):
        """Test create file POST endpoint."""
        response = client.post(
            "/create_file",
            data={"file_name": "test.txt", "file_type": "text"},
        )
        assert response.status_code in [404, 307, 400, 422, 500]

    def test_download_file_post(self, client):
        """Test download file POST endpoint."""
        response = client.post(
            "/download_file",
            data={"euid": "FI1", "download_type": "flat", "create_metadata_file": "no", "ret_json": "1"},
        )
        assert response.status_code in [404, 307, 400, 422, 500]

    def test_create_file_set_post(self, client):
        """Test create file set POST endpoint."""
        response = client.post(
            "/create_file_set",
            data={"name": "test_set", "file_euids": "FI1,FI2"},
        )
        assert response.status_code in [404, 307, 400, 422, 500]

    def test_bulk_create_files_from_tsv_post(self, client):
        """Test bulk create files from TSV POST endpoint."""
        import io
        tsv_content = "file_name\tfile_type\ntest.txt\ttext\n"
        files = {"file": ("test.tsv", io.BytesIO(tsv_content.encode()), "text/tab-separated-values")}
        response = client.post("/bulk_create_files_from_tsv", files=files)
        assert response.status_code in [404, 307, 400, 422, 500]


class TestInstanceCreationEndpoints:
    """Tests for instance creation endpoints."""

    def test_create_instance_form_get(self, client):
        """Test create instance form GET endpoint."""
        response = client.get("/create_instance/EX1")
        assert response.status_code in [404, 307, 400, 422, 500]

    def test_create_instance_post(self, client):
        """Test create instance POST endpoint."""
        response = client.post(
            "/create_instance",
            data={"template_euid": "EX1", "name": "Test Instance"},
        )
        assert response.status_code in [404, 307, 400, 422, 500]


class TestAdminTemplateEndpoints:
    """Tests for admin template endpoints."""

    def test_admin_template_post(self, client):
        """Test admin template POST endpoint."""
        response = client.post(
            "/admin_template",
            data={"euid": "EX1", "controlled_properties": "{}"},
        )
        assert response.status_code in [404, 307, 400, 410, 422, 500]


class TestLogoutEndpoint:
    """Tests for logout endpoint."""

    def test_logout_redirects(self, client):
        """Test logout endpoint redirects."""
        response = client.get("/logout", follow_redirects=False)
        # Should redirect to login or home
        assert response.status_code in [200, 302, 303, 307]


class TestOAuthEndpoints:
    """Tests for OAuth endpoints."""

    def test_oauth_callback_get_without_code(self, client):
        """Test OAuth callback GET without authorization code."""
        response = client.get("/oauth_callback")
        # Should handle missing code gracefully
        assert response.status_code in [200, 302, 307, 400, 422]

    def test_oauth_callback_post_without_code(self, client):
        """Test OAuth callback POST without authorization code."""
        response = client.post("/oauth_callback", json={})
        assert response.status_code in [200, 302, 307, 400, 422]


class TestLoginPostEndpoint:
    """Tests for login POST endpoint."""

    def test_login_post_without_email(self, client):
        """Test login POST without email."""
        response = client.post("/login", data={})
        # Should handle missing email
        assert response.status_code in [200, 302, 307, 400, 422]

    def test_login_post_with_email(self, client):
        """Test login POST with email."""
        response = client.post("/login", data={"email": "test@example.com"})
        # Should redirect to OAuth or handle login
        assert response.status_code in [200, 302, 307, 400, 422]


class TestPlateVisualizationEndpoints:
    """Tests for plate visualization endpoints."""

    def test_plate_visualization(self, client):
        """Test plate visualization endpoint."""
        response = client.get("/plate_visualization")
        # May require auth or specific parameters
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_plate_carousel(self, client):
        """Test plate carousel endpoint."""
        response = client.get("/plate_carosel2")
        # May require auth
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_vertical_exp(self, client):
        """Legacy vertical experiment endpoint is retired."""
        response = client.get("/vertical_exp")
        assert response.status_code in [302, 307, 400, 404, 422, 500]

    def test_get_related_plates(self, client):
        """Test get related plates endpoint."""
        response = client.get("/get_related_plates")
        assert response.status_code in [200, 302, 307, 400, 422, 500]


class TestDagEndpoints:
    """Tests for DAG visualization endpoints."""

    def test_dagg_endpoint(self, client):
        """Test DAG endpoint."""
        response = client.get("/dagg")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_dindex2_endpoint(self, client):
        """Test dindex2 endpoint."""
        response = client.get("/dindex2")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_get_dagv2(self, client):
        """Test get DAG v2 endpoint."""
        response = client.get("/get_dagv2")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_update_dag_requires_data(self, client):
        """Test update DAG requires proper data."""
        response = client.post("/update_dag", json={})
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_add_new_edge_requires_data(self, client):
        """Test add new edge requires proper data."""
        response = client.post("/add_new_edge", json={})
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_delete_node_requires_data(self, client):
        """Test delete node requires proper data."""
        response = client.post("/delete_node", json={})
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_delete_edge_requires_data(self, client):
        """Test delete edge requires proper data."""
        response = client.post("/delete_edge", json={})
        assert response.status_code in [200, 302, 307, 400, 422, 500]


@pytest.mark.skip(reason="Workflow GUI routes are retired in queue-centric Bloom beta.")
class TestWorkflowEndpoints:
    """Tests for workflow-related endpoints."""

    def test_workflow_summary(self, client):
        """Test workflow summary endpoint."""
        response = client.get("/workflow_summary")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_workflow_details_requires_uuid(self, client):
        """Test workflow details requires UUID."""
        response = client.get("/workflow_details")
        # Should return error without UUID parameter
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_legacy_step_action_route_missing(self, client):
        """Legacy workflow step action endpoint must stay unavailable."""
        legacy_path = "/workflow_step" + "_action"
        response = client.post(legacy_path, json={})
        assert response.status_code == 404

    def test_update_accordion_state(self, client):
        """Test update accordion state endpoint."""
        response = client.post("/update_accordion_state", json={"state": {}})
        assert response.status_code in [200, 302, 307, 400, 422, 500]


class TestObjectManagementEndpoints:
    """Tests for object management endpoints."""

    def test_uuid_details_removed(self, client):
        """Legacy UUID details endpoint must be removed."""
        response = client.get("/uuid_details")
        assert response.status_code == 404

    def test_un_delete_by_uuid_removed(self, client):
        """Legacy undelete alias endpoint must be removed."""
        response = client.get("/un_delete_by_uuid")
        assert response.status_code == 404

    def test_delete_by_euid(self, client):
        """Test delete by EUID endpoint."""
        response = client.get("/delete_by_euid")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_delete_object_requires_data(self, client):
        """Test delete object requires data."""
        response = client.post("/delete_object", json={})
        # 404 is valid if route requires specific content-type
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_update_obj_json_addl_properties(self, client):
        """Test update object JSON addl properties."""
        response = client.post("/update_obj_json_addl_properties", data={"obj_euid": "EX1"})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_update_object_name(self, client):
        """Test update object name endpoint."""
        response = client.get("/update_object_name")
        assert response.status_code in [200, 302, 307, 400, 422, 500]


class TestReportEndpoints:
    """Tests for report endpoints."""

    def test_bloom_schema_report(self, client):
        """Test bloom schema report endpoint."""
        response = client.get("/bloom_schema_report")
        assert response.status_code in [302, 307, 400, 410, 422, 500]

    def test_visual_report(self, client):
        """Test visual report endpoint."""
        response = client.get("/visual_report")
        assert response.status_code in [404, 302, 307, 400, 410, 422, 500]


class TestFileSetEndpoints:
    """Tests for file set endpoints."""

    def test_create_file_set(self, client):
        """Test create file set endpoint."""
        response = client.post("/create_file_set", json={"name": "test_set"})
        assert response.status_code in [404, 302, 307, 400, 422, 500]

    def test_search_file_sets(self, client):
        """Test search file sets endpoint."""
        response = client.post("/search_file_sets", json={"query": "test"})
        assert response.status_code in [404, 302, 307, 400, 410, 422, 500]

    def test_file_set_urls(self, client):
        """Test file set URLs endpoint."""
        response = client.get("/file_set_urls")
        assert response.status_code in [404, 302, 307, 400, 422, 500]


class TestInstanceCreationEndpoints:
    """Tests for instance creation endpoints."""

    def test_create_from_template_get(self, client):
        """Test create from template GET endpoint."""
        response = client.get("/create_from_template")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_create_from_template_post(self, client):
        """Test create from template POST endpoint."""
        response = client.post("/create_from_template", json={})
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_create_instance_endpoint(self, client):
        """Test create instance POST endpoint."""
        response = client.post("/create_instance", json={"template_euid": "TEST1"})
        assert response.status_code in [404, 302, 307, 400, 422, 500]


class TestBulkOperationEndpoints:
    """Tests for bulk operation endpoints."""

    def test_bulk_create_files(self, client):
        """Test bulk create files endpoint."""
        response = client.get("/bulk_create_files")
        assert response.status_code in [404, 302, 307, 400, 410, 422, 500]

    def test_bulk_create_files_from_tsv(self, client):
        """Test bulk create files from TSV endpoint."""
        response = client.post("/bulk_create_files_from_tsv", json={})
        assert response.status_code in [404, 302, 307, 400, 422, 500]


class TestNodePropertyEndpoints:
    """Tests for node property endpoints."""

    def test_get_node_property(self, client):
        """Test get node property endpoint."""
        response = client.get("/get_node_property")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_get_node_info(self, client):
        """Test get node info endpoint."""
        response = client.get("/get_node_info")
        assert response.status_code in [200, 302, 307, 400, 422, 500]


class TestScriptsEndpoint:
    """Tests for scripts endpoint."""

    def test_list_scripts(self, client):
        """Test list scripts endpoint."""
        response = client.get("/list-scripts")
        assert response.status_code in [200, 302, 307, 400, 422, 500]
        if response.status_code == 200:
            # Should return JSON
            data = response.json()
            assert isinstance(data, (list, dict))


class TestHealthEndpoints:
    """Tests for health check endpoints."""

    def test_health_check(self, client):
        """Test main health check endpoint."""
        response = client.get("/health")
        assert response.status_code in [200, 307, 404]

    def test_health_check_with_trailing_slash(self, client):
        """Test health check endpoint with trailing slash."""
        response = client.get("/health/")
        assert response.status_code in [200, 307, 404]

    def test_liveness_probe(self, client):
        """Test Kubernetes liveness probe."""
        response = client.get("/health/live")
        assert response.status_code in [200, 307, 404]

    def test_readiness_probe(self, client):
        """Test Kubernetes readiness probe."""
        response = client.get("/health/ready")
        assert response.status_code in [200, 307, 404, 503]


class TestJSONDataEndpoints:
    """Tests for JSON data manipulation endpoints."""

    def test_save_json_update(self, client):
        """Legacy save JSON update endpoint must be removed."""
        response = client.post("/save_json_update", json={"euid": "EX1", "json_data": {}})
        assert response.status_code == 404

    def test_update_json_value(self, client):
        """Legacy update JSON value endpoint must be removed."""
        response = client.post("/update_json_value", json={"euid": "EX1", "key": "test_key", "value": "test_value"})
        assert response.status_code == 404


class TestControlsAndReagentsEndpoints:
    """Tests for controls and reagents endpoints."""

    def test_controls_overview(self, client):
        """Test controls overview endpoint."""
        response = client.get("/controls")
        # Route may not exist
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_reagents_overview(self, client):
        """Test reagents overview endpoint."""
        response = client.get("/reagents")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_equipment_overview(self, client):
        """Test equipment overview endpoint."""
        response = client.get("/equipment")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


@pytest.mark.skip(reason="Workflow GUI routes are retired in queue-centric Bloom beta.")
class TestWorkflowManagementEndpoints:
    """Tests for workflow management endpoints."""

    def test_workflows_overview(self, client):
        """Test workflows overview endpoint."""
        response = client.get("/workflows")
        # Route may not exist or redirect
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_workflow_summary(self, client):
        """Test workflow summary endpoint."""
        response = client.get("/workflow_summary")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_workflow_details_with_euid(self, client):
        """Test workflow details with EUID."""
        response = client.get("/workflow_details?euid=WF1")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestAssaysEndpoints:
    """Tests for retired assay endpoints."""

    def test_assays_page(self, client):
        """Retired assays page endpoint should not be mounted."""
        response = client.get("/assays")
        assert response.status_code == 404


class TestAuditLogEndpoints:
    """Tests for audit log endpoints."""

    def test_audit_log_by_user(self, client):
        """Test audit log by user endpoint."""
        response = client.get("/audit_log_by_user")
        # Route may not exist
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_audit_log_with_user_param(self, client):
        """Test audit log with user parameter."""
        response = client.get("/audit_log_by_user?user=test@example.com")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestStaticFileEndpoints:
    """Tests for static file serving endpoints."""

    def test_static_css(self, client):
        """Test static CSS file serving."""
        response = client.get("/static/modern/css/bloom_modern.css")
        assert response.status_code in [200, 404]
        if response.status_code == 200:
            assert "text/css" in response.headers.get("content-type", "")

    def test_static_js(self, client):
        """Test static JS file serving."""
        response = client.get("/static/modern/js/bloom_modern.js")
        assert response.status_code in [200, 404]

    def test_removed_legacy_css(self, client):
        """Legacy CSS file should not be part of active runtime assets."""
        removed_asset = "/static/" + "legacy/style.css"
        response = client.get(removed_asset)
        assert response.status_code == 404


class TestDatabaseStatisticsEndpoints:
    """Tests for database statistics endpoints."""

    def test_database_statistics(self, client):
        """Test database statistics page."""
        response = client.get("/database_statistics")
        assert response.status_code in [200, 302, 307, 400, 422, 500]


class TestObjectTemplatesEndpoints:
    """Tests for object templates endpoints."""

    def test_object_templates_summary(self, client):
        """Test object templates summary endpoint."""
        response = client.get("/object_templates_summary")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_bloom_schema_report(self, client):
        """Test BLOOM schema report endpoint."""
        response = client.get("/bloom_schema_report")
        assert response.status_code in [302, 307, 400, 410, 422, 500]


class TestQueueEndpoints:
    """Tests for queue endpoints."""

    def test_queue_details_no_params(self, client):
        """Test queue details without parameters."""
        response = client.get("/queue_details")
        assert response.status_code in [200, 302, 307, 400, 422, 500]

    def test_queue_details_with_euid(self, client):
        """Test queue details with EUID."""
        response = client.get("/queue_details?euid=Q1")
        assert response.status_code in [200, 302, 307, 400, 422, 500]


class TestActionEndpoints:
    """Tests for action endpoints."""

    def test_get_action_groups(self, client):
        """Test get action groups endpoint."""
        response = client.get("/get_action_groups")
        # Route may not exist
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_execute_action_get(self, client):
        """Test execute action GET endpoint."""
        response = client.get("/execute_action")
        # Route may not exist
        assert response.status_code in [200, 302, 307, 400, 404, 405, 422, 500]

    def test_execute_action_post(self, client):
        """Test execute action POST endpoint."""
        response = client.post("/execute_action", json={
            "action_euid": "ACT1",
            "target_euid": "TGT1"
        })
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestFileOperationEndpoints:
    """Tests for file operation endpoints."""

    def test_download_file(self, client):
        """Test download file endpoint."""
        response = client.post(
            "/download_file",
            data={"euid": "FI1", "download_type": "flat", "create_metadata_file": "no", "ret_json": "1"},
        )
        assert response.status_code in [404, 302, 307, 400, 422, 500]

    def test_create_file(self, client):
        """Test create file endpoint."""
        response = client.post("/create_file", json={})
        assert response.status_code in [404, 302, 307, 400, 422, 500]

    def test_create_file_set(self, client):
        """Test create file set endpoint."""
        response = client.post("/create_file_set", json={})
        assert response.status_code in [404, 302, 307, 400, 422, 500]

    def test_search_files(self, client):
        """Test search files endpoint."""
        response = client.post("/search_files", json={"query": "test"})
        assert response.status_code in [404, 302, 307, 400, 410, 422, 500]

    def test_search_file_sets(self, client):
        """Test search file sets endpoint."""
        response = client.post("/search_file_sets", json={"query": "test"})
        assert response.status_code in [404, 302, 307, 400, 410, 422, 500]


class TestWorkflowOperationEndpoints:
    """Tests for workflow operation endpoints."""

    def test_legacy_step_action_is_retired(self, client):
        """Legacy workflow step action endpoint must be retired."""
        legacy_path = "/workflow_step" + "_action"
        response = client.post(legacy_path, json={})
        assert response.status_code == 404


class TestObjectOperationEndpoints:
    """Tests for object operation endpoints."""

    def test_delete_object(self, client):
        """Test delete object endpoint."""
        response = client.post("/delete_object", json={"euid": "invalid"})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_delete_by_euid(self, client):
        """Test delete by EUID endpoint."""
        response = client.get("/delete_by_euid?euid=INVALID1")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_un_delete_by_uuid_missing_euid(self, client):
        """Legacy undelete alias route must not exist for UUID queries."""
        response = client.get("/un_delete_by_uuid?uuid=invalid-uuid")
        assert response.status_code == 404


class TestDAGOperationEndpoints:
    """Tests for DAG operation endpoints."""

    def test_add_new_edge(self, client):
        """Deprecated add_new_edge must require canonical EUID keys."""
        response = client.post("/add_new_edge", json={})
        assert response.status_code == 400

    def test_delete_edge(self, client):
        """Test delete edge endpoint."""
        response = client.post("/delete_edge", json={})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_delete_node(self, client):
        """Test delete node endpoint."""
        response = client.post("/delete_node", json={})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_update_dag(self, client):
        """Test update DAG endpoint."""
        response = client.post("/update_dag", json={})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestSearchAndFilterEndpoints:
    """Tests for search and filter endpoints."""

    def test_set_filter(self, client):
        """Test set filter endpoint."""
        response = client.get("/set_filter")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_query_by_euids(self, client):
        """Test query by EUIDs endpoint."""
        response = client.post("/query_by_euids", json={"euids": []})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestGenericTemplatesEndpoints:
    """Tests for generic templates endpoints."""

    def test_generic_templates_post(self, client):
        """Test generic templates POST endpoint."""
        response = client.post("/generic_templates", json={})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestObjectCreationEndpoints:
    """Tests for object creation endpoints."""

    def test_create_from_template_get(self, client):
        """Test create from template GET endpoint."""
        response = client.get("/create_from_template")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_create_from_template_post(self, client):
        """Test create from template POST endpoint."""
        response = client.post("/create_from_template", json={})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_create_instance_post(self, client):
        """Test create instance POST endpoint."""
        response = client.post("/create_instance", json={})
        assert response.status_code in [404, 302, 307, 400, 422, 500]


class TestPreferencesEndpoints:
    """Tests for user preferences endpoints."""

    def test_update_preference(self, client):
        """Test update preference endpoint."""
        response = client.post("/update_preference", json={
            "key": "test_pref",
            "value": "test_value"
        })
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_update_accordion_state(self, client):
        """Test update accordion state endpoint."""
        response = client.post("/update_accordion_state", json={
            "accordion_id": "test",
            "state": "open"
        })
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestJSONUpdateEndpoints:
    """Tests for JSON update endpoints."""

    def test_update_obj_json_addl_properties(self, client):
        """Test update object JSON addl properties endpoint."""
        response = client.post("/update_obj_json_addl_properties", data={"obj_euid": "EX1", "test_key": "test"})
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_save_json_addl_key(self, client):
        """Test save JSON addl key endpoint."""
        response = client.post(
            "/save_json_addl_key",
            json={"euid": "EX1", "json_addl_key": "test", "json_data": {"value": "test"}},
        )
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


class TestOAuthEndpoints:
    """Tests for OAuth endpoints."""

    def test_oauth_callback_get(self, client):
        """Test OAuth callback GET endpoint."""
        response = client.get("/oauth_callback")
        assert response.status_code in [200, 302, 307, 400, 401, 404, 422, 500]

    def test_oauth_callback_post(self, client):
        """Test OAuth callback POST endpoint."""
        response = client.post("/oauth_callback", json={})
        assert response.status_code in [200, 302, 307, 400, 401, 404, 422, 500]


class TestAdditionalViewEndpoints:
    """Tests for additional view endpoints."""

    def test_dindex2(self, client):
        """Test dindex2 endpoint."""
        response = client.get("/dindex2")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_vertical_exp(self, client):
        """Test vertical exp endpoint."""
        response = client.get("/vertical_exp")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_plate_carosel2(self, client):
        """Test plate carosel2 endpoint."""
        response = client.get("/plate_carosel2")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_get_related_plates(self, client):
        """Test get related plates endpoint."""
        response = client.get("/get_related_plates")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]

    def test_lims(self, client):
        """Test lims endpoint."""
        response = client.get("/lims")
        assert response.status_code in [200, 302, 307, 400, 404, 422, 500]


@pytest.mark.skip(reason="Workflow GUI routes are retired in queue-centric Bloom beta.")
class TestWorkflowQueryParamCompatibility:
    """Compatibility tests for workflow query parameter names."""

    def test_workflow_details_accepts_workflow_euid_param(self, client):
        """workflow_details should accept workflow_euid query parameter (new workflow UI contract)."""
        response = client.get("/workflow_details?workflow_euid=NONEXISTENT_WORKFLOW_EUID")
        # The route may render an error for missing workflow, but should not fail validation.
        assert response.status_code != 422
