# -*- coding: utf-8 -*-
"""Location: ./tests/playwright/pages/mcp_registry_page.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

MCP Registry page object for browsing and registering MCP servers.
"""

# Standard
from typing import Callable

# Third-Party
from playwright.sync_api import expect, Locator
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError

# Local
from .base_page import BasePage


class MCPRegistryPage(BasePage):
    """Page object for MCP Registry features."""

    def _wait_for_registry_refresh(self, action: Callable[[], None], timeout: int = 30000) -> None:
        """Wait for an HTMX registry partial refresh triggered by an action."""
        previous_markup = self.registry_servers_container.inner_html(timeout=timeout)

        try:
            with self.page.expect_response(lambda response: "/admin/mcp-registry/partial" in response.url and response.request.method == "GET", timeout=timeout) as response_info:
                action()
            response = response_info.value
        except PlaywrightTimeoutError as exc:
            raise AssertionError("Timed out waiting for MCP Registry partial refresh") from exc

        if response.status >= 400:
            raise AssertionError(f"MCP Registry partial refresh failed with status {response.status}")

        self.page.wait_for_function(
            """([selector, previous]) => {
                const el = document.querySelector(selector);
                return !!el && el.innerHTML !== previous;
            }""",
            arg=["#mcp-registry-servers", previous_markup],
            timeout=timeout,
        )

        self.wait_for_registry_results_ready(timeout=timeout)

    # ==================== Panel Elements ====================

    @property
    def registry_panel(self) -> Locator:
        """MCP Registry panel container."""
        return self.page.locator("#mcp-registry-panel")

    @property
    def registry_servers_container(self) -> Locator:
        """MCP Registry servers container (HTMX target)."""
        return self.page.locator("#mcp-registry-servers")

    # ==================== Overview Card ====================

    @property
    def overview_card(self) -> Locator:
        """MCP Registry overview card with gradient background."""
        return self.registry_servers_container.locator(".bg-gradient-to-r").first

    @property
    def total_servers_count(self) -> Locator:
        """Total servers count display."""
        return self.overview_card.locator("div.text-3xl.font-bold").first

    @property
    def registered_servers_count(self) -> Locator:
        """Registered servers count display."""
        return self.overview_card.locator("div.text-3xl.font-bold").nth(1)

    @property
    def categories_count(self) -> Locator:
        """Categories count display."""
        return self.overview_card.locator("div.text-3xl.font-bold").nth(2)

    @property
    def refresh_catalog_btn(self) -> Locator:
        """Refresh catalog button."""
        return self.overview_card.locator('button:has-text("Refresh")')

    # ==================== Filter Elements ====================

    @property
    def filters_card(self) -> Locator:
        """Filters card container."""
        return self.registry_servers_container.locator(".bg-white.rounded-lg.shadow-sm").first

    @property
    def category_filter(self) -> Locator:
        """Category filter dropdown."""
        return self.page.locator("#category-filter")

    @property
    def auth_filter(self) -> Locator:
        """Auth type filter dropdown."""
        return self.page.locator("#auth-filter")

    @property
    def search_input(self) -> Locator:
        """Search input field."""
        return self.page.locator("#search-input")

    # ==================== Statistics Cards ====================

    @property
    def categories_card(self) -> Locator:
        """Categories statistics card."""
        return self.registry_servers_container.locator(".bg-white.rounded-lg.shadow").first

    @property
    def auth_types_card(self) -> Locator:
        """Authentication types statistics card."""
        return self.registry_servers_container.locator(".bg-white.rounded-lg.shadow").nth(1)

    @property
    def providers_card(self) -> Locator:
        """Providers statistics card."""
        return self.registry_servers_container.locator(".bg-white.rounded-lg.shadow").nth(2)

    def get_category_badge(self, category: str) -> Locator:
        """Get a specific category badge by name.

        Args:
            category: Category name (e.g., "Software Development")

        Returns:
            Locator for the category badge
        """
        return self.categories_card.locator(f'span:has-text("{category}")')

    def get_auth_type_item(self, auth_type: str) -> Locator:
        """Get a specific auth type item by name.

        Args:
            auth_type: Auth type name (e.g., "OAuth2.1", "API Key")

        Returns:
            Locator for the auth type item
        """
        return self.auth_types_card.locator(f'span:has-text("{auth_type}")').first

    def get_provider_badge(self, provider: str) -> Locator:
        """Get a specific provider badge by name.

        Args:
            provider: Provider name (e.g., "GitHub", "Stripe")

        Returns:
            Locator for the provider badge
        """
        return self.providers_card.locator(f'span:has-text("{provider}")')

    # ==================== Server Grid ====================

    @property
    def server_grid(self) -> Locator:
        """Server grid container."""
        return self.page.locator("#server-grid")

    @property
    def server_cards(self) -> Locator:
        """All server cards in the grid."""
        return self.server_grid.locator(".server-card:visible")

    def get_server_card_by_name(self, server_name: str) -> Locator:
        """Get a specific server card by name.

        Args:
            server_name: Server name (e.g., "GitHub", "Stripe")

        Returns:
            Locator for the server card
        """
        return self.server_grid.locator(f'.server-card:has(h3:has-text("{server_name}"))')

    def get_server_add_button(self, server_name: str) -> Locator:
        """Get the 'Add Server' button for a specific server.

        Args:
            server_name: Server name

        Returns:
            Locator for the add button
        """
        return self.get_server_card_by_name(server_name).locator('button:has-text("Add Server")')

    # ==================== API Key Modal ====================

    @property
    def api_key_modal(self) -> Locator:
        """API Key modal container within MCP registry servers."""
        return self.page.locator("#mcp-registry-servers #api-key-modal")

    @property
    def modal_server_name(self) -> Locator:
        """Server name in modal header."""
        return self.api_key_modal.locator("#modal-server-name")

    @property
    def modal_custom_name_input(self) -> Locator:
        """Custom name input in modal."""
        return self.api_key_modal.locator("#modal-custom-name")

    @property
    def modal_api_key_input(self) -> Locator:
        """API key input in modal."""
        return self.api_key_modal.locator("#modal-api-key")

    @property
    def modal_register_button(self) -> Locator:
        """Register button in modal."""
        return self.api_key_modal.locator('button:has-text("Register Server")')

    @property
    def modal_close_button(self) -> Locator:
        """Close button in modal."""
        return self.api_key_modal.locator('button[onclick*="closeApiKeyModal"]')

    # ==================== High-Level Navigation Methods ====================

    def navigate_to_registry_tab(self) -> None:
        """Navigate to MCP Registry tab and wait for HTMX content to load."""
        self.sidebar.click_registry_tab()
        # The HTMX partial loads asynchronously after a 300ms debounce.
        # Wait for the #server-grid element that only exists in the partial response.
        self.page.wait_for_selector("#server-grid", state="attached", timeout=60000)

    # ==================== High-Level Filter Operations ====================

    def select_category(self, category: str) -> None:
        """Select a category from the filter dropdown.

        Args:
            category: Category name to select
        """
        if self.category_filter.input_value(timeout=10000) == category:
            return

        self._wait_for_registry_refresh(lambda: self.category_filter.select_option(category))
        expect(self.category_filter).to_have_value(category, timeout=10000)

    def select_auth_type(self, auth_type: str) -> None:
        """Select an auth type from the filter dropdown.

        Args:
            auth_type: Auth type to select (e.g., "OAuth2.1", "API Key")
        """
        if self.auth_filter.input_value(timeout=10000) == auth_type:
            return

        self._wait_for_registry_refresh(lambda: self.auth_filter.select_option(auth_type))
        expect(self.auth_filter).to_have_value(auth_type, timeout=10000)

    def search_servers(self, query: str) -> None:
        """Search for servers using the search input.

        Args:
            query: Search query string
        """
        if self.search_input.input_value(timeout=10000) == query:
            return

        def _apply_search() -> None:
            # Use real typing events because the HTMX search binding is wired to
            # ``keyup changed delay:500ms``. ``fill()`` updates the value but does
            # not reliably exercise the debounce path this UI uses in production.
            self.search_input.click()
            self.search_input.press("Control+A")
            self.search_input.press("Backspace")
            if query:
                self.search_input.type(query, delay=30)

        self._wait_for_registry_refresh(_apply_search)
        expect(self.search_input).to_have_value(query, timeout=10000)

    def clear_filters(self) -> None:
        """Clear all filters and search.

        Note: Due to HTMX hx-include behavior, clearing filters sequentially can cause
        race conditions where one filter's HTMX request includes stale values from other filters.
        We clear all inputs first, then trigger a single HTMX request.
        """
        # Clear all inputs without triggering HTMX
        self.page.evaluate("""
            document.getElementById('category-filter').value = '';
            document.getElementById('auth-filter').value = '';
            document.getElementById('search-input').value = '';
        """)

        # Trigger a single HTMX request and wait for the response to land.
        with self.page.expect_response("**/admin/mcp-registry/partial**", timeout=5000):
            self.category_filter.select_option("")

        # Wait for HTMX swap to complete and DOM to settle
        self.wait_for_registry_results_ready(timeout=10000)

    def click_category_badge(self, category: str) -> None:
        """Click on a category badge to filter by that category.

        Args:
            category: Category name to filter by
        """
        badge = self.get_category_badge(category)
        badge.click()
        self.page.wait_for_timeout(1000)

    def click_auth_type_item(self, auth_type: str) -> None:
        """Click on an auth type item to filter by that type.

        Args:
            auth_type: Auth type to filter by
        """
        item = self.get_auth_type_item(auth_type)
        item.click()
        self.page.wait_for_timeout(1000)

    # ==================== High-Level Server Operations ====================

    def get_server_count(self) -> int:
        """Get the number of servers currently displayed.

        Returns:
            Number of visible server cards
        """
        # Wait for grid to be stable before counting
        self.page.wait_for_selector("#server-grid", state="attached", timeout=10000)

        # Wait for HTMX requests to complete (no .htmx-request class on body or forms)
        self.page.wait_for_function(
            """() => {
                const hasHtmxRequest = document.body.classList.contains('htmx-request');
                const forms = document.querySelectorAll('form.htmx-request');
                return !hasHtmxRequest && forms.length === 0;
            }""",
            timeout=10000,
        )

        # Brief wait for DOM rendering after HTMX swap
        self.page.wait_for_timeout(300)
        return self.server_cards.count()

    def get_total_servers_count(self) -> int:
        """Get the total servers count from overview card.

        Returns:
            Total servers count as integer
        """
        count_text = self.total_servers_count.text_content().strip()
        return int(count_text)

    def get_registered_servers_count(self) -> int:
        """Get the registered servers count from overview card.

        Returns:
            Registered servers count as integer
        """
        count_text = self.registered_servers_count.text_content().strip()
        return int(count_text)

    def get_categories_count(self) -> int:
        """Get the categories count from overview card.

        Returns:
            Categories count as integer
        """
        count_text = self.categories_count.text_content().strip()
        return int(count_text)

    def refresh_catalog(self) -> None:
        """Click the refresh catalog button."""
        self.refresh_catalog_btn.click()
        self.page.wait_for_timeout(1000)

    def server_exists(self, server_name: str) -> bool:
        """Check if a server with the given name exists in the grid.

        Args:
            server_name: Server name to check

        Returns:
            True if server exists, False otherwise
        """
        return self.get_server_card_by_name(server_name).count() > 0

    # ==================== High-Level Modal Operations ====================

    def open_add_server_modal(self, server_name: str) -> None:
        """Open the API key modal for a specific server.

        Args:
            server_name: Server name to add
        """
        add_button = self.get_server_add_button(server_name)
        add_button.click()
        expect(self.api_key_modal).to_be_visible(timeout=5000)

    def is_modal_visible(self) -> bool:
        """Check if the API key modal is visible.

        Returns:
            True if modal is visible, False otherwise
        """
        return self.api_key_modal.is_visible()

    def close_modal(self) -> None:
        """Close the API key modal."""
        self.modal_close_button.click()
        expect(self.api_key_modal).to_be_hidden(timeout=5000)

    def fill_api_key_form(self, api_key: str, custom_name: str = None) -> None:
        """Fill the API key form in the modal.

        Args:
            api_key: API key to enter
            custom_name: Optional custom name for the server
        """
        if custom_name:
            self.modal_custom_name_input.fill(custom_name)
        self.modal_api_key_input.fill(api_key)

    def submit_api_key_form(self) -> None:
        """Submit the API key form."""
        self.modal_register_button.click()

    def register_server_with_api_key(self, server_name: str, api_key: str, custom_name: str = None) -> None:
        """Complete flow to register a server with API key.

        Args:
            server_name: Server name to register
            api_key: API key for authentication
            custom_name: Optional custom name for the server
        """
        self.open_add_server_modal(server_name)
        self.fill_api_key_form(api_key, custom_name)
        self.submit_api_key_form()
        # Wait for registration to complete and server grid to refresh
        self.page.wait_for_timeout(2000)

    # ==================== High-Level Verification Methods ====================

    def get_server_card_info(self, server_name: str) -> dict:
        """Get information from a server card.

        Args:
            server_name: Server name

        Returns:
            Dictionary with server card information
        """
        card = self.get_server_card_by_name(server_name)

        return {
            "name": card.locator("h3").text_content().strip(),
            "description": card.locator("p.text-sm").text_content().strip(),
            "provider": card.locator('div:has-text("Provider:")').text_content().replace("Provider:", "").strip(),
            "url": card.locator('div:has-text("URL:")').text_content().replace("URL:", "").strip(),
            "auth_badge": card.locator("span.bg-yellow-100, span.bg-green-100").first.text_content().strip() if card.locator("span.bg-yellow-100, span.bg-green-100").count() > 0 else None,
            "category_badge": card.locator("span.bg-purple-100").first.text_content().strip() if card.locator("span.bg-purple-100").count() > 0 else None,
        }

    def wait_for_registry_loaded(self, timeout: int = 60000) -> None:
        """Wait for MCP Registry panel to be loaded and ready.

        Args:
            timeout: Maximum time to wait in milliseconds
        """
        expect(self.registry_panel).to_be_visible(timeout=timeout)
        expect(self.overview_card).to_be_visible(timeout=timeout)
        expect(self.server_grid).to_be_visible(timeout=timeout)

    def wait_for_registry_results_ready(self, timeout: int = 60000) -> None:
        """Wait for the registry panel and result container after a filter refresh."""
        expect(self.registry_panel).to_be_visible(timeout=timeout)
        expect(self.overview_card).to_be_visible(timeout=timeout)
        self.page.wait_for_selector("#server-grid", state="attached", timeout=timeout)

    def verify_filter_applied(self, filter_type: str, filter_value: str) -> bool:
        """Verify that a filter has been applied correctly.

        Args:
            filter_type: Type of filter ("category" or "auth")
            filter_value: Expected filter value

        Returns:
            True if filter is applied correctly
        """
        if filter_type == "category":
            selected = self.category_filter.input_value(timeout=10000)
            return selected == filter_value
        if filter_type == "auth":
            selected = self.auth_filter.input_value(timeout=10000)
            return selected == filter_value
        return False
