"""Snapshot engine for open-webview-e2e CLI."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

import yaml


@dataclass
class ElementRef:
    """Element reference with metadata."""

    ref: str
    tag: str
    text: str
    selector: str
    attrs: Dict[str, str] = field(default_factory=dict)
    index: int = 0

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "ref": self.ref,
            "tag": self.tag,
            "text": self.text,
            "selector": self.selector,
            "attrs": self.attrs,
            "index": self.index,
        }

    def format_short(self) -> str:
        """Format as short reference."""
        text_display = self.text[:30] + "..." if len(self.text) > 30 else self.text
        return f"{self.ref} [{self.tag} '{text_display}']"


@dataclass
class PageSnapshot:
    """Page snapshot with element references."""

    url: str
    title: str
    elements: List[ElementRef] = field(default_factory=list)
    timestamp: Optional[datetime] = None
    session_name: Optional[str] = None

    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = datetime.now()

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "url": self.url,
            "title": self.title,
            "elements": [e.to_dict() for e in self.elements],
            "timestamp": self.timestamp.isoformat() if self.timestamp else None,
            "session": self.session_name,
        }

    def format_output(self) -> str:
        """Format as human-readable output."""
        lines = []
        lines.append("### Page")
        lines.append(f"- Page URL: {self.url}")
        lines.append(f"- Page Title: {self.title}")

        if self.elements:
            lines.append("### Elements")
            for elem in self.elements[:50]:
                lines.append(f"- {elem.format_short()} | {elem.selector}")

            if len(self.elements) > 50:
                lines.append(f"- ... and {len(self.elements) - 50} more elements")

        return "\n".join(lines)

    def to_yaml(self) -> str:
        """Convert to YAML format."""
        return yaml.dump(self.to_dict(), default_flow_style=False, allow_unicode=True)

    def find_by_ref(self, ref: str) -> Optional[ElementRef]:
        """Find element by reference."""
        for elem in self.elements:
            if elem.ref == ref or f"e{elem.index}" == ref:
                return elem
        return None

    def find_by_selector(self, selector: str) -> Optional[ElementRef]:
        """Find element by selector."""
        for elem in self.elements:
            if elem.selector == selector:
                return elem
        return None


class SnapshotEngine:
    """Engine for capturing and managing page snapshots."""

    CLICKABLE_TAGS = {"a", "button", "input", "select", "textarea"}
    CLICKABLE_TYPES = {"submit", "button", "reset", "image", "radio", "checkbox"}

    def __init__(self, test_id_attr: str = "data-testid"):
        self.test_id_attr = test_id_attr
        self._current_snapshot: Optional[PageSnapshot] = None

    def capture(self, page: Any, session_name: Optional[str] = None) -> PageSnapshot:
        """Capture current page snapshot."""
        try:
            url = page.url if hasattr(page, "url") else str(page.evaluate("() => window.location.href"))
            title = page.title() if hasattr(page, "title") else str(page.evaluate("() => document.title"))

            elements = self._extract_elements(page)

            snapshot = PageSnapshot(
                url=url,
                title=title,
                elements=elements,
                session_name=session_name,
            )

            self._current_snapshot = snapshot
            return snapshot

        except Exception as e:
            raise RuntimeError(f"Failed to capture snapshot: {e}")

    def _extract_elements(self, page: Any) -> List[ElementRef]:
        """Extract clickable elements from page."""
        elements = []

        try:
            element_data = page.evaluate(
                """
                () => {
                    const elements = [];
                    const seen = new Set();

                    document.querySelectorAll('a, button, input, select, textarea').forEach((el, idx) => {
                        if (seen.has(el)) return;
                        seen.add(el);

                        const tag = el.tagName.toLowerCase();
                        let text = '';

                        if (tag === 'input') {
                            const type = el.type.toLowerCase();
                            text = el.placeholder || el.id || type;
                        } else {
                            text = el.textContent?.trim() || el.innerText?.trim() || '';
                        }

                        if (text.length > 50) {
                            text = text.substring(0, 50) + '...';
                        }

                        let selector = '';
                        if (el.id) {
                            selector = '#' + el.id;
                        } else if (el.className && typeof el.className === 'string' && el.className.trim()) {
                            const classes = el.className.trim().split(/\\s+/).slice(0, 2);
                            selector = tag + '.' + classes.join('.');
                        } else {
                            selector = tag;
                        }

                        const attrs = {};
                        if (el.name) attrs['name'] = el.name;
                        if (el.type) attrs['type'] = el.type;
                        if (el.href) attrs['href'] = el.href.substring(0, 100);

                        elements.push({
                            tag,
                            text,
                            selector,
                            attrs,
                            index: idx
                        });
                    });

                    return elements;
                }
                """
            )

            for i, data in enumerate(element_data):
                ref = f"e{i + 1}"
                element = ElementRef(
                    ref=ref,
                    tag=data.get("tag", ""),
                    text=data.get("text", ""),
                    selector=data.get("selector", ""),
                    attrs=data.get("attrs", {}),
                    index=i,
                )
                elements.append(element)

        except Exception as e:
            print(f"Warning: Failed to extract elements: {e}")

        return elements

    def save_snapshot(self, snapshot: PageSnapshot, filename: Optional[str] = None, output_dir: str = ".") -> str:
        """Save snapshot to file."""
        if filename is None:
            timestamp = datetime.now().strftime("%Y-%m-%dT%H-%M-%S-%f")[:-3] + "Z"
            filename = f"snapshot-{timestamp}.yaml"

        output_path = Path(output_dir)
        output_path.mkdir(parents=True, exist_ok=True)

        filepath = output_path / filename
        with open(filepath, "w", encoding="utf-8") as f:
            f.write(snapshot.to_yaml())

        return str(filepath)

    def get_current_snapshot(self) -> Optional[PageSnapshot]:
        """Get current snapshot."""
        return self._current_snapshot

    def resolve_ref(self, ref_or_selector: str) -> str:
        """Resolve reference or selector to actual selector."""
        if self._current_snapshot:
            element = self._current_snapshot.find_by_ref(ref_or_selector)
            if element:
                return element.selector
        return ref_or_selector


_engine: Optional[SnapshotEngine] = None


def get_snapshot_engine(test_id_attr: str = "data-testid") -> SnapshotEngine:
    """Get global snapshot engine instance."""
    global _engine
    if _engine is None:
        _engine = SnapshotEngine(test_id_attr)
    return _engine
