"""Restricted web view with URL whitelist enforcement for Kiosk-mode."""

import fnmatch
import hashlib
import logging
from pathlib import Path
from typing import List, Optional, Union

from PySide6.QtCore import QStandardPaths, QUrl
from PySide6.QtGui import QDesktopServices
from PySide6.QtWebEngineCore import (
    QWebEngineNavigationRequest,
    QWebEnginePage,
    QWebEngineProfile,
    QWebEngineSettings,
)
from PySide6.QtWebEngineWidgets import QWebEngineView

logger = logging.getLogger(__name__)


class CustomWebEnginePage(QWebEnginePage):
    """Custom page that handles new window requests for downloads."""

    def acceptNavigationRequest(
        self, url: Union[QUrl, str], nav_type: QWebEnginePage.NavigationType, is_main_frame: bool
    ) -> bool:
        """Handle navigation requests, including download links.

        Args:
            url: Target URL (QUrl or string)
            nav_type: Type of navigation (link click, form submit, etc.)
            is_main_frame: Whether this is the main frame

        Returns:
            True to accept navigation, False to reject
        """
        # Convert to string if QUrl
        url_str = url.toString() if isinstance(url, QUrl) else url

        # Log all navigation attempts for debugging
        logger.debug(f"Navigation request: {url_str} (type={nav_type}, main_frame={is_main_frame})")

        # Check if this might be a download (common file extensions)
        download_extensions = [
            ".pdf",
            ".zip",
            ".rar",
            ".7z",
            ".tar",
            ".gz",
            ".doc",
            ".docx",
            ".xls",
            ".xlsx",
            ".ppt",
            ".pptx",
            ".jpg",
            ".jpeg",
            ".png",
            ".gif",
            ".bmp",
            ".svg",
            ".mp4",
            ".mp3",
            ".avi",
            ".mov",
            ".wav",
            ".exe",
            ".msi",
            ".dmg",
            ".pkg",
            ".csv",
            ".txt",
            ".json",
            ".xml",
        ]

        if any(url_str.lower().endswith(ext) for ext in download_extensions):
            logger.debug(f"Detected potential download URL: {url_str}")
            # This will trigger downloadRequested if it's a download

        return super().acceptNavigationRequest(url, nav_type, is_main_frame)

    def createWindow(self, window_type: QWebEnginePage.WebWindowType) -> QWebEnginePage:
        """Handle new window requests (target=_blank, window.open, etc.).

        Many downloads are triggered via target="_blank" links.

        Args:
            window_type: Type of window being created

        Returns:
            New page instance for the window
        """
        logger.debug(f"New window requested, type: {window_type}")

        # Create a temporary page to handle the download
        # This page will never be displayed but allows downloads to work
        download_page = QWebEnginePage(self.profile(), self)

        logger.debug("Created temporary page for download/popup")

        # Return the temporary page - it will trigger downloadRequested if it's a download
        return download_page


class RestrictedWebEngineView(QWebEngineView):
    """Web view that enforces URL whitelist for Kiosk-mode security.

    If allowed_urls is empty, no restrictions are applied.
    If allowed_urls is not empty, only matching URLs are loaded in the view.
    Non-matching URLs open in the system default browser.

    Each webapp_url gets an isolated profile to prevent cache corruption
    from old domains affecting new domains.
    """

    def __init__(self, allowed_urls: Optional[List[str]] = None, webapp_url: Optional[str] = None):
        """Initialize the restricted web view.

        Args:
            allowed_urls: List of allowed URL patterns (empty = no restriction)
                         Patterns support wildcards: *.example.com, localhost, etc.
            webapp_url: The web application URL for profile isolation. If provided,
                       creates a unique profile per domain to avoid cache corruption.
        """
        super().__init__()
        self.allowed_urls = allowed_urls or []
        self.webapp_url = webapp_url

        # Create persistent profile for cookie and session storage
        # Profile is unique per domain to prevent cache corruption
        self.profile = self._create_persistent_profile()

        # Configure WebEngine settings on the profile for proper JavaScript and mouse event support
        settings = self.profile.settings()

        # Enable JavaScript (required for mouseover events and interactive features)
        settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True)

        # Enable JavaScript access to clipboard (some web apps need this)
        settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, True)

        # Enable JavaScript to open windows (for dialogs, popups)
        settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows, True)

        # Enable local content access (needed for drag operations)
        settings.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True)

        # Allow local content to access remote resources (some web apps may need this)
        settings.setAttribute(
            QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, False
        )

        logger.debug(
            "RestrictedWebEngineView WebEngine settings configured: "
            "JavaScript=enabled, Clipboard=enabled, WindowOpen=enabled, LocalFileAccess=enabled"
        )

        # Use custom page for better download handling with persistent profile
        custom_page = CustomWebEnginePage(self.profile, self)
        self.setPage(custom_page)

        logger.debug(
            "RestrictedWebEngineView initialized with CustomWebEnginePage and persistent profile"
        )

        # CRITICAL: Also configure settings on the page itself after setPage()
        # This ensures Page-level settings override Profile defaults for event handling
        page_settings = self.page().settings()
        page_settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True)
        page_settings.setAttribute(
            QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, True
        )
        page_settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows, True)
        page_settings.setAttribute(
            QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True
        )
        page_settings.setAttribute(
            QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, False
        )

        logger.debug("Page-level WebEngine settings configured for mouse event handling")

        # Connect to navigation request handler
        self.page().navigationRequested.connect(self._on_navigation_requested)

    def _create_persistent_profile(self) -> QWebEngineProfile:
        """Create and configure a persistent web engine profile.

        This enables persistent cookies and cache storage, allowing
        authentication sessions (e.g., Microsoft login) to persist
        across application restarts.

        Each unique webapp domain gets its own profile to prevent
        cache corruption from old domains affecting new domains.

        Returns:
            Configured QWebEngineProfile with persistent storage
        """
        # Get application data directory
        app_data_dir = QStandardPaths.writableLocation(
            QStandardPaths.StandardLocation.AppDataLocation
        )

        # Create unique profile name based on webapp_url domain
        # This ensures different domains get isolated profiles
        if self.webapp_url:
            # Extract domain/path for profile naming
            if self.webapp_url.startswith("http://") or self.webapp_url.startswith("https://"):
                # Remote URL - use domain
                url_obj = QUrl(self.webapp_url)
                domain = url_obj.host() or "remote"
            else:
                # Local file - use hash of path
                domain = "local"
        else:
            domain = "default"

        # Create a stable hash of the domain
        # This creates a unique but consistent profile name per domain
        domain_hash = hashlib.md5(domain.encode()).hexdigest()[:8]
        profile_name = f"webdrop_bridge_{domain_hash}"

        # Create profile directory path
        profile_path = Path(app_data_dir) / "webdrop_bridge" / profile_name
        profile_path.mkdir(parents=True, exist_ok=True)

        # Create persistent profile with custom storage location
        # Using unique profile name so different domains have isolated caches
        profile = QWebEngineProfile(profile_name)
        profile.setPersistentStoragePath(str(profile_path))

        # Configure persistent cookies (critical for authentication)
        profile.setPersistentCookiesPolicy(
            QWebEngineProfile.PersistentCookiesPolicy.ForcePersistentCookies
        )

        # Enable HTTP cache for better performance
        profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.DiskHttpCache)

        # Set cache size to 100 MB
        profile.setHttpCacheMaximumSize(100 * 1024 * 1024)

        logger.debug(f"Created persistent profile '{profile_name}' at: {profile_path}")
        logger.debug(f"Profile domain identifier: {domain}")
        logger.debug("Cookies policy: ForcePersistentCookies")
        logger.debug("HTTP cache: DiskHttpCache (100 MB)")

        return profile

    def _on_navigation_requested(self, request: QWebEngineNavigationRequest) -> None:
        """Handle navigation requests and enforce URL whitelist.

        Args:
            request: Navigation request to process
        """
        url = request.url()

        # If no restrictions, allow all URLs
        if not self.allowed_urls:
            return

        # Check if URL matches whitelist
        if self._is_url_allowed(url):
            # Allow the navigation (default behavior)
            return

        # URL not whitelisted - open in system browser
        request.reject()
        QDesktopServices.openUrl(url)

    def _is_url_allowed(self, url: QUrl) -> bool:
        """Check if a URL matches the whitelist.

        Supports:
        - Exact domain matches: example.com
        - Wildcard patterns: *.example.com
        - Localhost variations: localhost, 127.0.0.1
        - Internal/local URLs: file://, data:, about:, blob:, qrc:

        Args:
            url: QUrl to check

        Returns:
            True if URL is allowed, False otherwise
        """
        url_str = url.toString()
        host = url.host()
        scheme = url.scheme()

        # Allow internal browser/Qt schemes (never send these to the OS)
        if scheme in ("file", "data", "about", "blob", "qrc"):
            return True

        # If no whitelist, allow all URLs
        if not self.allowed_urls:
            return True

        # Check against whitelist patterns
        for pattern in self.allowed_urls:
            # Check full URL pattern
            if fnmatch.fnmatch(url_str, pattern):
                return True

            # Check host pattern (for http/https URLs)
            if scheme in ("http", "https") and host:
                if fnmatch.fnmatch(host, pattern):
                    return True

                # Also check with www prefix variants
                if fnmatch.fnmatch(f"www.{host}", pattern):
                    return True
                if fnmatch.fnmatch(host.replace("www.", ""), pattern):
                    return True

        return False

    def clear_cache_and_cookies(self) -> None:
        """Clear the profile cache and cookies.

        Use this method when the webapp URL changes to prevent cache corruption
        from old domains affecting the new domain's authentication.
        """
        logger.debug(f"Clearing cache and cookies for profile: {self.profile.storageName()}")

        # Clear all cookies
        self.profile.cookieStore().deleteAllCookies()

        # Clear cache
        self.profile.clearHttpCache()

        logger.debug("Cache and cookies cleared successfully")
