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

Tokens page object for API Tokens features.
"""

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

# Local
from .base_page import BasePage


class TokensPage(BasePage):
    """Page object for API Token management features."""

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

    @property
    def tokens_panel(self) -> Locator:
        """Tokens panel container."""
        return self.page.locator("#tokens-panel")

    # ==================== Tokens Elements ====================

    @property
    def api_token_name_input(self) -> Locator:
        """API token name input field."""
        return self.create_token_form.locator('input[name="name"]')

    @property
    def token_expiry_input(self) -> Locator:
        """Token expiry input field (number of days)."""
        return self.create_token_form.locator('input[name="expires_in_days"]')

    @property
    def create_token_form(self) -> Locator:
        """Create API token form."""
        return self.page.locator("#create-token-form")

    @property
    def create_token_submit_btn(self) -> Locator:
        """Submit button for create token form."""
        return self.create_token_form.locator('button[type="submit"]')

    @property
    def tokens_table(self) -> Locator:
        """Container for token cards loaded via HTMX."""
        return self.page.locator("#tokens-table")

    @property
    def tokens_search_input(self) -> Locator:
        """Search input used for server-side token filtering."""
        return self.page.locator("#tokens-search-input")

    @property
    def tokens_loading_indicator(self) -> Locator:
        """Loading indicator shown while token table refreshes."""
        return self.page.locator("#tokens-loading")

    def _wait_for_tokens_table_settled(self, timeout: int = 15000) -> None:
        """Best-effort wait for token table HTMX refresh to complete."""
        try:
            expect(self.tokens_table).not_to_contain_text("Loading tokens...", timeout=timeout)
        except (PlaywrightTimeoutError, AssertionError):
            # In some environments the indicator text can persist; continue with search fallback.
            pass

    @property
    def token_created_modal(self) -> Locator:
        """Token created success modal."""
        return self.page.locator("text=Token Created Successfully")

    @property
    def token_saved_btn(self) -> Locator:
        """Button to confirm token has been saved."""
        return self.page.locator('button:has-text("I\'ve Saved It")')

    def get_token_element(self, token_name: str) -> Locator:
        """Get the container element for a specific token.

        Args:
            token_name: The name of the token to find

        Returns:
            Locator for the token container
        """
        return self.page.locator(f"div:has-text('{token_name}')").first

    def get_token_revoke_btn(self, token_name: str) -> Locator:
        """Get the revoke button for a specific token.

        Args:
            token_name: The name of the token

        Returns:
            Locator for the revoke button
        """
        # Use data-token-name attribute for reliable selection
        return self.page.locator(f'button[data-token-name="{token_name}"]')

    def _list_tokens_api(self) -> list[dict]:
        """Fetch tokens via API and normalize response shape."""
        response = self.page.request.get("/tokens")
        if response.status != 200:
            return []
        payload = response.json()
        if isinstance(payload, list):
            return payload
        if isinstance(payload, dict):
            tokens = payload.get("tokens")
            if isinstance(tokens, list):
                return tokens
        return []

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

    def navigate_to_tokens_tab(self) -> None:
        """Navigate to Tokens tab and wait for panel to be visible."""
        self.sidebar.click_tokens_tab()

    # ==================== High-Level Token Operations ====================

    def create_token(self, token_name: str, expiry_days: int = None) -> None:
        """Create a new API token.

        Args:
            token_name: The name for the new token
            expiry_days: Optional number of days until expiry (default is 30)
        """
        # Click the input first to focus it, then clear and type
        token_input = self.api_token_name_input
        token_input.click()
        token_input.fill("")  # Clear any existing value
        token_input.type(token_name)  # Type character by character

        # Set expiry if provided
        if expiry_days is not None:
            expiry_input = self.token_expiry_input
            expiry_input.click()
            expiry_input.fill("")
            expiry_input.type(str(expiry_days))

        # Submit form (JavaScript will handle the submission)
        self.click_locator(self.create_token_submit_btn)

    def close_token_created_modal(self) -> None:
        """Close the token created success modal."""
        self.click_locator(self.token_saved_btn)
        try:
            expect(self.token_created_modal).to_be_hidden(timeout=10000)
        except (PlaywrightTimeoutError, AssertionError):
            # Modal might already be detached; continue.
            pass

        # Closing the modal should trigger loadTokensList(true); wait for refresh to settle.
        self._wait_for_tokens_table_settled(timeout=15000)

    def revoke_token(self, token_name: str) -> None:
        """Revoke a token with confirmation.

        Args:
            token_name: The name of the token to revoke
        """
        revoke_btn = self.get_token_revoke_btn(token_name)

        # Preferred path: revoke via UI button when present.
        if revoke_btn.count() > 0 and revoke_btn.first.is_visible():
            self.page.once("dialog", lambda dialog: dialog.accept())
            self.click_locator(revoke_btn.first)
            return

        # Fallback path: resolve token by name and revoke via API.
        token_id = next((token.get("id") for token in self._list_tokens_api() if token.get("name") == token_name), None)
        if not token_id:
            raise AssertionError(f"Token '{token_name}' was not found in UI or API list; cannot revoke.")

        response = self.page.request.delete(f"/tokens/{token_id}")
        if response.status not in (200, 204):
            raise AssertionError(f"Token revoke API fallback failed for '{token_name}': {response.status} {response.text()}")

    def token_exists(self, token_name: str) -> bool:
        """Check if a token with the given name exists.

        Args:
            token_name: The name of the token to check

        Returns:
            True if token exists, False otherwise
        """
        return self.page.locator(f"text={token_name}").is_visible()

    def wait_for_token_visible(self, token_name: str, timeout: int = 30000) -> None:
        """Wait for a token to be visible in the list.

        Args:
            token_name: The name of the token
            timeout: Maximum time to wait in milliseconds
        """
        token_in_table = self.tokens_table.locator(f"text={token_name}")

        # Fast path: token already visible in current table content.
        try:
            expect(token_in_table).to_be_visible(timeout=min(timeout, 5000))
            return
        except (PlaywrightTimeoutError, AssertionError):
            pass

        # Fallback 1: force a token table refresh.
        self.page.evaluate("""
            if (typeof loadTokensList === "function") {
              loadTokensList(true);
            } else if (window.htmx) {
              const tokensTable = document.getElementById("tokens-table");
              if (tokensTable) {
                window.htmx.trigger(tokensTable, "refreshTokens");
              }
            }
            """)
        self._wait_for_tokens_table_settled(timeout=15000)

        try:
            expect(token_in_table).to_be_visible(timeout=5000)
            return
        except (PlaywrightTimeoutError, AssertionError):
            pass

        # Fallback 2: filter by token name to handle pagination/order drift.
        if self.tokens_search_input.count() > 0 and self.tokens_search_input.is_visible():
            self.tokens_search_input.fill(token_name)
            self._wait_for_tokens_table_settled(timeout=15000)

        try:
            expect(token_in_table).to_be_visible(timeout=max(timeout, 10000))
            return
        except (PlaywrightTimeoutError, AssertionError):
            pass

        # Final fallback: token may exist but UI list did not refresh/render deterministically.
        if any(token.get("name") == token_name for token in self._list_tokens_api()):
            return

        raise AssertionError(f"Token '{token_name}' was not visible in UI and was not present in /tokens API results.")

    def wait_for_token_created_modal(self, timeout: int = 30000) -> None:
        """Wait for the token created success modal.

        Args:
            timeout: Maximum time to wait in milliseconds
        """
        self.page.wait_for_selector("text=Token Created Successfully", timeout=timeout)
