"""PageAnalyzer — extract metadata from a rendered page.

Runs inside the Playwright page context and returns structured information
about the current page: title, internal links, forms with fields, buttons,
and text inputs.
"""

from __future__ import annotations

import logging
import re
from typing import Any
from urllib.parse import urljoin, urlparse

from playwright.sync_api import Page

log = logging.getLogger("antiqa.crawler")


class PageAnalyzer:
    """Inspect a live Playwright page and extract structural metadata."""

    def __init__(self, page: Page, base_url: str) -> None:
        self.page = page
        self.base_url = base_url.rstrip("/")
        self._base_host = urlparse(base_url).netloc

    def analyze(self) -> dict[str, Any]:
        """Return a dict with all discoverable metadata for the current page."""
        return {
            "url": self.page.url,
            "url_pattern": self.url_to_pattern(self.page.url),
            "title": self._get_title(),
            "links": self._get_internal_links(),
            "forms": self._get_forms(),
            "buttons": self._get_buttons(),
            "inputs": self._get_inputs(),
        }

    # ── URL helpers ────────────────────────────────────

    def url_to_pattern(self, url: str) -> str:
        """Normalise a URL into a pattern (replace numeric path segments with {id})."""
        parsed = urlparse(url)
        path = parsed.path.rstrip("/") or "/"
        path = re.sub(r"/\d+", "/{id}", path)
        return path

    def is_internal(self, url: str) -> bool:
        """Check if a URL belongs to the same application."""
        parsed = urlparse(url)
        if not parsed.netloc:
            return True
        return parsed.netloc == self._base_host

    def resolve(self, href: str) -> str | None:
        """Resolve a relative href to an absolute URL, or None if external."""
        if not href or href.startswith(("#", "javascript:", "mailto:", "tel:")):
            return None
        absolute = urljoin(self.page.url, href)
        if self.is_internal(absolute):
            return absolute
        return None

    # ── Extractors ─────────────────────────────────────

    def _get_title(self) -> str:
        try:
            return self.page.title()
        except Exception:
            return ""

    def _get_internal_links(self) -> list[str]:
        """Return deduplicated list of internal link URLs on the page."""
        hrefs = self.page.eval_on_selector_all(
            "a[href]",
            "els => els.map(e => e.getAttribute('href'))",
        )
        seen: set[str] = set()
        links: list[str] = []
        for href in hrefs:
            resolved = self.resolve(href)
            if resolved and resolved not in seen:
                seen.add(resolved)
                links.append(resolved)
        return links

    def _get_forms(self) -> list[dict[str, Any]]:
        """Extract form metadata: action, method, id, and field selectors."""
        forms_data: list[dict[str, Any]] = self.page.evaluate("""
            () => {
                return Array.from(document.querySelectorAll('form')).map(form => {
                    const fields = Array.from(
                        form.querySelectorAll('input, select, textarea')
                    ).map(el => {
                        const id = el.id ? '#' + el.id : '';
                        const name = el.name ? `[name="${el.name}"]` : '';
                        return id || name || el.tagName.toLowerCase();
                    });
                    return {
                        selector: form.id ? '#' + form.id : 'form',
                        action: form.action || '',
                        method: (form.method || 'get').toUpperCase(),
                        fields: fields,
                    };
                });
            }
        """)
        return forms_data

    def _get_buttons(self) -> list[str]:
        """Return CSS selectors for all clickable buttons."""
        return self.page.evaluate("""
            () => {
                const buttons = document.querySelectorAll(
                    'button, input[type="submit"], input[type="button"], [role="button"]'
                );
                return Array.from(buttons).map(el => {
                    if (el.id) return '#' + el.id;
                    if (el.className && typeof el.className === 'string') {
                        const cls = el.className.trim().split(/\\s+/)[0];
                        if (cls) return el.tagName.toLowerCase() + '.' + cls;
                    }
                    return el.tagName.toLowerCase();
                });
            }
        """)

    def _get_inputs(self) -> list[str]:
        """Return CSS selectors for text-like input fields (outside forms too)."""
        return self.page.evaluate("""
            () => {
                const inputs = document.querySelectorAll(
                    'input:not([type="hidden"]):not([type="submit"]):not([type="button"]), textarea, select'
                );
                return Array.from(inputs).map(el => {
                    if (el.id) return '#' + el.id;
                    if (el.name) return `[name="${el.name}"]`;
                    return el.tagName.toLowerCase();
                });
            }
        """)
