"""CLI entry point for open-webview-e2e.

Key Design Principles:
1. Session Persistence: Sessions are saved to ~/.open-webview-e2e/sessions.json
   and auto-reconnected between CLI invocations.
2. Structured Output: All commands output JSON for LLM consumption.
3. Token Efficiency: Avoid outputting large data (screenshots, HTML) directly.
"""

from __future__ import annotations

import json
import sys
from typing import Any, Dict, Optional

import click

from nomo_e2e.cli.config import load_config, CLIConfig
from nomo_e2e.cli.session import get_session_manager
from nomo_e2e.cli.snapshot import get_snapshot_engine


class CLIContext:
    """CLI context object."""

    def __init__(self, config: CLIConfig):
        self.config = config
        self.session_manager = get_session_manager()
        self.snapshot_engine = get_snapshot_engine(config.test_id_attribute)


def json_output(data: Dict[str, Any]) -> None:
    """Output JSON response for LLM consumption."""
    click.echo(json.dumps(data, ensure_ascii=False, indent=2))


def success_output(message: str, **kwargs) -> None:
    """Output success response in JSON format."""
    data = {"status": "success", "message": message}
    data.update(kwargs)
    json_output(data)


def get_page_or_error(session) -> Any:
    """Get Playwright Page object from session or raise error.

    Args:
        session: Session object.

    Returns:
        Page object.

    Raises:
        RuntimeError: If Page is not available (e.g., WebDriver adapter).
    """
    from nomo_e2e.adapters.webdriver import WebDriverAdapter

    if isinstance(session.controller._adapter, WebDriverAdapter):
        raise RuntimeError(
            "Advanced interactions (hover, dblclick, press, select, check, etc.) "
            "are not supported by WebDriver protocol. "
            "Use CDP protocol (Electron, Edge) for these features."
        )

    if hasattr(session.controller._adapter, "_page"):
        return session.controller._adapter._page

    raise RuntimeError(
        "Page object not available. Ensure you are using CDP protocol with a compatible adapter."
    )


def get_page_safe(session) -> Any:
    """Get Playwright Page object from session, returns None if not available.

    Args:
        session: Session object.

    Returns:
        Page object or None.
    """
    from nomo_e2e.adapters.webdriver import WebDriverAdapter

    if isinstance(session.controller._adapter, WebDriverAdapter):
        return None

    if hasattr(session.controller._adapter, "_page"):
        return session.controller._adapter._page
    return None


@click.group()
@click.option("-s", "--session", "session_name", help="Session name")
@click.option("--config", "config_path", help="Config file path")
@click.option("--cdp-endpoint", help="CDP endpoint URL")
@click.option("--webdriver-endpoint", help="WebDriver endpoint URL")
@click.option("--driver", default="auto", help="Driver type: auto, cdp, webdriver")
@click.option("--browser", default="chromium", help="Browser type: chromium, firefox, webkit")
@click.option("--output-dir", default="./screenshots", help="Output directory")
@click.option("-v", "--verbose", is_flag=True, help="Verbose output")
@click.pass_context
def cli(
    ctx: click.Context,
    session_name: Optional[str],
    config_path: Optional[str],
    cdp_endpoint: Optional[str],
    webdriver_endpoint: Optional[str],
    driver: str,
    browser: str,
    output_dir: str,
    verbose: bool,
):
    """Open WebView E2E Testing CLI.

    A CLI tool for testing WebView applications using CDP and WebDriver protocols.
    """
    config = load_config(config_path)

    if cdp_endpoint:
        config.cdp_endpoint = cdp_endpoint
    if webdriver_endpoint:
        config.webdriver_endpoint = webdriver_endpoint
    if driver != "auto":
        config.driver = driver
    if browser:
        config.browser = browser
    if output_dir:
        config.output_dir = output_dir

    ctx.obj = CLIContext(config)
    ctx.obj.verbose = verbose

    if session_name:
        ctx.obj.session_manager.switch_session(session_name)


@cli.command()
@click.pass_context
@click.argument("url", required=False)
@click.option("--cdp-endpoint", "endpoint", help="CDP endpoint URL (e.g., http://localhost:9222)")
@click.option(
    "--webdriver-endpoint", "endpoint", help="WebDriver endpoint URL (e.g., http://localhost:4445)"
)
@click.option("--driver", default="auto", help="Driver type: auto, cdp, webdriver")
@click.option("--name", "session_name", help="Session name")
def open_cmd(
    ctx: click.Context,
    url: Optional[str],
    endpoint: Optional[str],
    driver: str,
    session_name: Optional[str],
):
    """Open a WebView connection.

    Examples:
        open http://localhost:9222
        open --driver cdp http://localhost:9222
        open -s myapp
    """
    cli_ctx: CLIContext = ctx.obj
    config = cli_ctx.config

    if url is None and endpoint is None:
        if config.cdp_endpoint:
            url = config.cdp_endpoint
        elif config.webdriver_endpoint:
            url = config.webdriver_endpoint
        else:
            click.echo("Error: No URL or endpoint provided.", err=True)
            sys.exit(1)

    target_url = url or endpoint
    session_name = session_name or f"default-{len(cli_ctx.session_manager.list_sessions())}"

    try:
        session = cli_ctx.session_manager.create_session(session_name, target_url, driver)

        page = get_page_safe(session)

        if page is not None:
            snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
            success_output(
                f"Connected to {target_url}",
                session=session.name,
                url=snapshot.url,
                title=snapshot.title,
                elements=[
                    {
                        "ref": e.ref,
                        "tag": e.tag,
                        "text": e.text[:50],
                        "selector": e.selector,
                    }
                    for e in snapshot.elements[:20]
                ],
                element_count=len(snapshot.elements),
            )
        else:
            json_output(
                {
                    "status": "success",
                    "message": f"Connected to {target_url}",
                    "session": session.name,
                    "url": session.url or target_url,
                    "title": session.title or "",
                    "driver": driver,
                    "note": "Snapshot requires CDP protocol. Use 'eval' for basic interactions.",
                }
            )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def close_cmd(ctx: click.Context):
    """Close the current WebView connection."""
    cli_ctx: CLIContext = ctx.obj

    session = cli_ctx.session_manager.get_current_session()
    if session is None:
        json_output({"status": "success", "message": "No active session to close."})
        return

    session.disconnect()
    cli_ctx.session_manager.close_session(session.name)
    success_output(f"Session '{session.name}' closed.")


@cli.command()
@click.pass_context
def list_cmd(ctx: click.Context):
    """List all sessions."""
    cli_ctx: CLIContext = ctx.obj

    sessions = cli_ctx.session_manager.list_sessions()

    if not sessions:
        json_output({"status": "success", "sessions": [], "count": 0})
        return

    json_output(
        {
            "status": "success",
            "sessions": [
                {
                    "name": s.name,
                    "endpoint": s.endpoint,
                    "driver": s.driver,
                    "url": s.url,
                    "is_active": s.is_active,
                }
                for s in sessions
            ],
            "count": len(sessions),
        }
    )


@cli.command()
@click.pass_context
def close_all(ctx: click.Context):
    """Close all sessions."""
    cli_ctx: CLIContext = ctx.obj
    cli_ctx.session_manager.close_all()
    success_output("All sessions closed.")


@cli.command()
@click.pass_context
@click.argument("url")
def goto(ctx: click.Context, url: str):
    """Navigate to a URL."""
    cli_ctx: CLIContext = ctx.obj

    session = cli_ctx.session_manager.require_current_session()

    try:
        session.controller.navigate(url)

        json_output(
            {
                "status": "success",
                "message": f"Navigated to {url}",
                "url": session.url,
                "title": session.title,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector")
@click.option("--timeout", default=30000, help="Timeout in milliseconds")
def click_cmd(ctx: click.Context, selector: str, timeout: int):
    """Click an element.

    Examples:
        click "#submit"
        click "button.submit"
        click e1
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)

        if resolved_selector != selector:
            session.controller.wait_for_selector(resolved_selector, timeout_ms=timeout)

        session.controller.click(resolved_selector, timeout_ms=timeout)

        json_output(
            {
                "status": "success",
                "message": f"Clicked {resolved_selector}",
                "selector": resolved_selector,
                "url": session.url,
                "title": session.title,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("text")
@click.option("--delay", default=0, help="Delay between keystrokes in milliseconds")
def type_text(ctx: click.Context, text: str, delay: int):
    """Type text (press keys one by one).

    Examples:
        type-text "Hello World"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        script = (
            "(text) => {"
            "  const active = document.activeElement;"
            "  if (active) {"
            "    const len = active.value ? active.value.length : 0;"
            "    for(let i=0;i<len;i++) { active.setSelectionRange(len,len); }"
            "    active.value = text;"
            "    active.dispatchEvent(new Event('input', { bubbles: true }));"
            "  }"
            "}"
        )
        session.controller.execute_script(script, text)

        json_output(
            {
                "status": "success",
                "message": f"Typed: {text}",
                "text": text,
                "url": session.url,
                "title": session.title,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector")
@click.argument("text")
def fill(ctx: click.Context, selector: str, text: str):
    """Fill an input field (clear and type).

    Examples:
        fill "#username" "admin"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)
        session.controller.type_text(resolved_selector, text)

        json_output(
            {
                "status": "success",
                "message": f"Filled {resolved_selector}",
                "selector": resolved_selector,
                "text": text,
                "url": session.url,
                "title": session.title,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.option("--filename", help="Output filename")
@click.option("--selector", help="Element selector to screenshot")
def screenshot(ctx: click.Context, filename: Optional[str], selector: Optional[str]):
    """Take a screenshot.

    Examples:
        screenshot
        screenshot --filename page.png
    """
    cli_ctx: CLIContext = ctx.obj
    config = cli_ctx.config

    try:
        import os

        session = cli_ctx.session_manager.require_current_session()

        output_path = filename or os.path.join(config.output_dir, "screenshot.png")
        os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)

        session.controller.screenshot(output_path, selector=selector)

        file_size = os.path.getsize(output_path)
        json_output(
            {
                "status": "success",
                "message": f"Screenshot saved: {output_path}",
                "path": output_path,
                "filename": os.path.basename(output_path),
                "size_bytes": file_size,
                "size_kb": round(file_size / 1024, 2),
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.option("--filename", help="Output filename")
def snapshot_cmd(ctx: click.Context, filename: Optional[str]):
    """Capture page snapshot.

    Examples:
        snapshot
        snapshot --filename elements.yaml
    """
    cli_ctx: CLIContext = ctx.obj
    config = cli_ctx.config

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_safe(session)

        if page is not None:
            snapshot = cli_ctx.snapshot_engine.capture(page, session.name)

            if filename:
                path = cli_ctx.snapshot_engine.save_snapshot(snapshot, filename, config.output_dir)
                json_output(
                    {
                        "status": "success",
                        "message": f"Snapshot saved: {path}",
                        "path": path,
                        "element_count": len(snapshot.elements),
                    }
                )
            else:
                json_output(
                    {
                        "status": "success",
                        "url": snapshot.url,
                        "title": snapshot.title,
                        "elements": [
                            {
                                "ref": e.ref,
                                "tag": e.tag,
                                "text": e.text[:50],
                                "selector": e.selector,
                            }
                            for e in snapshot.elements[:50]
                        ],
                        "element_count": len(snapshot.elements),
                    }
                )
        else:
            elements = (
                session.controller.execute_script("""
                () => {
                    const elements = [];
                    const tags = document.querySelectorAll('a, button, input, select, textarea, [role]');
                    tags.forEach((el, i) => {
                        if (el.id) {
                            elements.push({
                                ref: 'e' + (i + 1),
                                tag: el.tagName.toLowerCase(),
                                text: el.textContent?.trim().substring(0, 50) || '',
                                selector: '#' + el.id
                            });
                        }
                    });
                    return elements.slice(0, 50);
                }
            """)
                or []
            )

            json_output(
                {
                    "status": "success",
                    "url": session.url,
                    "title": session.title,
                    "elements": elements,
                    "element_count": len(elements),
                    "note": "Basic snapshot via JavaScript. Use CDP for full DOM snapshot.",
                }
            )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("script")
def eval_cmd(ctx: click.Context, script: str):
    """Evaluate JavaScript expression.

    Examples:
        eval "document.title"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        result = session.controller.execute_script(script)

        json_output(
            {
                "status": "success",
                "script": script,
                "result": result,
                "result_type": type(result).__name__,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def url(ctx: click.Context):
    """Get current page URL."""
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        json_output({"status": "success", "url": session.url})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def title(ctx: click.Context):
    """Get current page title."""
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        json_output({"status": "success", "title": session.title})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def reload(ctx: click.Context):
    """Reload current page."""
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.reload_page()

        json_output(
            {
                "status": "success",
                "message": "Page reloaded",
                "url": session.url,
                "title": session.title,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def go_back(ctx: click.Context):
    """Go back to previous page."""
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.go_back()

        json_output(
            {
                "status": "success",
                "message": "Navigated back",
                "url": session.url,
                "title": session.title,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def go_forward(ctx: click.Context):
    """Go forward to next page."""
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.go_forward()

        json_output(
            {
                "status": "success",
                "message": "Navigated forward",
                "url": session.url,
                "title": session.title,
            }
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def status(ctx: click.Context):
    """Show current session status."""
    cli_ctx: CLIContext = ctx.obj

    session = cli_ctx.session_manager.get_current_session()

    if session is None:
        json_output({"status": "success", "message": "No active session.", "session": None})
        return

    json_output(
        {
            "status": "success",
            "session": {
                "name": session.name,
                "endpoint": session.endpoint,
                "driver": session.driver,
                "connected": session.is_connected,
                "url": session.url,
                "title": session.title,
            },
        }
    )


@cli.command()
@click.pass_context
@click.argument("filename", required=False)
def state_save(ctx: click.Context, filename: Optional[str]):
    """Save session state (cookies, localStorage) to file.

    Examples:
        state-save
        state-save ./my-state.json
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        if filename is None:
            filename = f"{session.name}-state.json"

        state = session.save_state(filename)

        json_output(
            {
                "status": "success",
                "message": f"State saved to {filename}",
                "filename": filename,
                "cookies_count": len(state.get("cookies", [])),
                "localStorage_count": len(state.get("localStorage", {})),
                "sessionStorage_count": len(state.get("sessionStorage", {})),
            }
        )
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("filename")
def state_load(ctx: click.Context, filename: str):
    """Load session state from file.

    Examples:
        state-load ./my-state.json
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.load_state(filename)

        json_output({"status": "success", "message": f"State loaded from {filename}"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("name", required=False)
def cookie_list(ctx: click.Context, name: Optional[str]):
    """List cookies.

    Examples:
        cookie-list
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        cookies = (
            session.controller.execute_script("""
            () => document.cookie.split(';').map(c => {
                const [name, ...rest] = c.trim().split('=');
                return { name, value: rest.join('=') };
            }).filter(c => c.name)
        """)
            or []
        )

        if name:
            cookies = [c for c in cookies if name in c.get("name", "")]

        json_output({"status": "success", "cookies": cookies, "count": len(cookies)})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def localstorage_list(ctx: click.Context):
    """List all localStorage items.

    Examples:
        localstorage-list
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        items = (
            session.controller.execute_script("""
            () => {
                const items = {};
                try {
                    for (let i = 0; i < localStorage.length; i++) {
                        const key = localStorage.key(i);
                        items[key] = localStorage.getItem(key);
                    }
                } catch (e) {}
                return items;
            }
        """)
            or {}
        )

        json_output({"status": "success", "items": items, "count": len(items)})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
@click.argument("value")
def localstorage_set(ctx: click.Context, key: str, value: str):
    """Set a localStorage item.

    Examples:
        localstorage-set theme dark
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.execute_script(
            "(key, value) => { localStorage.setItem(key, value); }", key, value
        )

        json_output({"status": "success", "message": f"localStorage['{key}'] = '{value}'"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
def localstorage_delete(ctx: click.Context, key: str):
    """Delete a localStorage item.

    Examples:
        localstorage-delete theme
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.execute_script("(key) => { localStorage.removeItem(key); }", key)

        json_output({"status": "success", "message": f"localStorage['{key}'] deleted"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def localstorage_clear(ctx: click.Context):
    """Clear all localStorage.

    Examples:
        localstorage-clear
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.execute_script("() => localStorage.clear()")

        json_output({"status": "success", "message": "localStorage cleared"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


# Phase 7.3: Advanced Interactions


@cli.command()
@click.pass_context
@click.argument("selector")
def hover(ctx: click.Context, selector: str):
    """Hover over an element.

    Examples:
        hover "#dropdown"
        hover "e1"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)
        page.hover(resolved_selector)

        snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
        success_output(
            f"Hovered {resolved_selector}",
            selector=resolved_selector,
            url=snapshot.url,
            title=snapshot.title,
            element_count=len(snapshot.elements),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector")
@click.option("--timeout", default=30000, help="Timeout in milliseconds")
def dblclick(ctx: click.Context, selector: str, timeout: int):
    """Double-click an element.

    Examples:
        dblclick "#item"
        dblclick "e1"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)
        page.dblclick(resolved_selector, timeout=timeout)

        snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
        success_output(
            f"Double-clicked {resolved_selector}",
            selector=resolved_selector,
            url=snapshot.url,
            title=snapshot.title,
            element_count=len(snapshot.elements),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
def press(ctx: click.Context, key: str):
    """Press a key.

    Examples:
        press Enter
        press Escape
        press Control+a
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        page.keyboard.press(key)

        snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
        success_output(
            f"Pressed key: {key}",
            key=key,
            url=snapshot.url,
            title=snapshot.title,
            element_count=len(snapshot.elements),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
def keydown(ctx: click.Context, key: str):
    """Press and hold a key.

    Examples:
        keydown Control
        keydown Shift
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        page.keyboard.down(key)

        success_output(f"Key down: {key}", key=key)

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
def keyup(ctx: click.Context, key: str):
    """Release a key.

    Examples:
        keyup Control
        keyup Shift
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        page.keyboard.up(key)

        success_output(f"Key up: {key}", key=key)

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector")
@click.argument("value")
def select(ctx: click.Context, selector: str, value: str):
    """Select an option from a dropdown.

    Examples:
        select "select#country" "US"
        select "e2" "value2"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)
        page.select_option(resolved_selector, value)

        snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
        success_output(
            f"Selected option '{value}' in {resolved_selector}",
            selector=resolved_selector,
            value=value,
            url=snapshot.url,
            title=snapshot.title,
            element_count=len(snapshot.elements),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector")
def check(ctx: click.Context, selector: str):
    """Check a checkbox or radio button.

    Examples:
        check "#agree"
        check "e1"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)
        page.check(resolved_selector)

        snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
        success_output(
            f"Checked {resolved_selector}",
            selector=resolved_selector,
            url=snapshot.url,
            title=snapshot.title,
            element_count=len(snapshot.elements),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector")
def uncheck(ctx: click.Context, selector: str):
    """Uncheck a checkbox.

    Examples:
        uncheck "#notify"
        uncheck "e1"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)
        page.uncheck(resolved_selector)

        snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
        success_output(
            f"Unchecked {resolved_selector}",
            selector=resolved_selector,
            url=snapshot.url,
            title=snapshot.title,
            element_count=len(snapshot.elements),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("x", type=int)
@click.argument("y", type=int)
def mousemove(ctx: click.Context, x: int, y: int):
    """Move mouse to coordinates.

    Examples:
        mousemove 100 200
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        page.mouse.move(x, y)

        success_output(f"Mouse moved to ({x}, {y})", x=x, y=y)

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("x", type=int, required=False)
@click.argument("y", type=int, required=False)
@click.option("--button", default="left", help="Mouse button: left, right, middle")
def mousedown(ctx: click.Context, x: Optional[int], y: Optional[int], button: str):
    """Press mouse button down.

    Examples:
        mousedown 100 200
        mousedown --button right
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        if x is not None and y is not None:
            page.mouse.move(x, y)
        page.mouse.down(button=button)

        success_output(f"Mouse button down: {button}", button=button, x=x, y=y)

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.option("--button", default="left", help="Mouse button: left, right, middle")
def mouseup(ctx: click.Context, button: str):
    """Release mouse button.

    Examples:
        mouseup
        mouseup --button right
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        page.mouse.up(button=button)

        success_output(f"Mouse button up: {button}", button=button)

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("delta_x", type=int, default=0)
@click.argument("delta_y", type=int, default=0)
def mousewheel(ctx: click.Context, delta_x: int, delta_y: int):
    """Scroll mouse wheel.

    Examples:
        mousewheel 0 -300
        mousewheel 100 0
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        page.mouse.wheel(delta_x, delta_y)

        success_output(
            f"Mouse wheel scrolled ({delta_x}, {delta_y})", delta_x=delta_x, delta_y=delta_y
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector")
@click.argument("file_path")
def upload(ctx: click.Context, selector: str, file_path: str):
    """Upload a file to an input element.

    Examples:
        upload "input[type=file]" "./document.pdf"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        import os

        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        if not os.path.exists(file_path):
            json_output({"status": "error", "error": f"File not found: {file_path}"})
            sys.exit(1)

        resolved_selector = cli_ctx.snapshot_engine.resolve_ref(selector)
        page.set_input_files(resolved_selector, file_path)

        file_size = os.path.getsize(file_path)
        success_output(
            f"File uploaded to {resolved_selector}",
            selector=resolved_selector,
            file=os.path.basename(file_path),
            file_size=file_size,
            file_size_kb=round(file_size / 1024, 2),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("selector_source")
@click.argument("selector_target")
def drag(ctx: click.Context, selector_source: str, selector_target: str):
    """Drag an element to a target location.

    Examples:
        drag "#source" "#target"
        drag "e1" "e2"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_or_error(session)

        resolved_source = cli_ctx.snapshot_engine.resolve_ref(selector_source)
        resolved_target = cli_ctx.snapshot_engine.resolve_ref(selector_target)

        source_element = page.locator(resolved_source)
        target_element = page.locator(resolved_target)

        source_element.drag_to(target_element)

        snapshot = cli_ctx.snapshot_engine.capture(page, session.name)
        success_output(
            f"Dragged {resolved_source} to {resolved_target}",
            source=resolved_source,
            target=resolved_target,
            url=snapshot.url,
            title=snapshot.title,
            element_count=len(snapshot.elements),
        )

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


# Phase 7.4: Storage and Network


@cli.command()
@click.pass_context
@click.argument("name", required=False)
def cookie_get(ctx: click.Context, name: Optional[str]):
    """Get cookies.

    Examples:
        cookie-get
        cookie-get "session_id"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        cookies = (
            session.controller.execute_script("""
            () => document.cookie.split(';').map(c => {
                const [name, ...rest] = c.trim().split('=');
                return { name, value: rest.join('=') };
            }).filter(c => c.name)
        """)
            or []
        )

        if name:
            cookies = [c for c in cookies if name in c.get("name", "")]

        json_output({"status": "success", "cookies": cookies, "count": len(cookies)})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("name")
@click.argument("value")
@click.option("--domain", help="Cookie domain")
@click.option("--path", default="/", help="Cookie path")
def cookie_set(ctx: click.Context, name: str, value: str, domain: Optional[str], path: str):
    """Set a cookie.

    Examples:
        cookie-set "token" "abc123"
        cookie-set "session" "xyz789" --domain ".example.com"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        cookie_str = f"{name}={value}; path={path}"
        if domain:
            cookie_str += f"; domain={domain}"

        session.controller.execute_script("(cookie) => { document.cookie = cookie; }", cookie_str)

        json_output(
            {
                "status": "success",
                "message": f"Cookie set: {name}={value}",
                "name": name,
                "value": value,
                "domain": domain,
                "path": path,
            }
        )
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("name")
def cookie_delete(ctx: click.Context, name: str):
    """Delete a cookie.

    Examples:
        cookie-delete "session_id"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        session.controller.execute_script(
            "(name) => { document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT;'; }",
            name,
        )

        json_output({"status": "success", "message": f"Cookie deleted: {name}", "name": name})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def cookie_clear(ctx: click.Context):
    """Clear all cookies.

    Examples:
        cookie-clear
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        session.controller.execute_script("""
            () => {
                document.cookie.split(';').forEach(c => {
                    const name = c.trim().split('=')[0];
                    if (name) {
                        document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT;';
                    }
                });
            }
        """)

        json_output({"status": "success", "message": "All cookies cleared"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def sessionstorage_list(ctx: click.Context):
    """List all sessionStorage items.

    Examples:
        sessionstorage-list
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        items = (
            session.controller.execute_script("""
            () => {
                const items = {};
                try {
                    for (let i = 0; i < sessionStorage.length; i++) {
                        const key = sessionStorage.key(i);
                        items[key] = sessionStorage.getItem(key);
                    }
                } catch (e) {}
                return items;
            }
        """)
            or {}
        )

        json_output({"status": "success", "items": items, "count": len(items)})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
@click.argument("value")
def sessionstorage_set(ctx: click.Context, key: str, value: str):
    """Set a sessionStorage item.

    Examples:
        sessionstorage-set "theme" "dark"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.execute_script(
            "(key, value) => { sessionStorage.setItem(key, value); }", key, value
        )

        json_output({"status": "success", "message": f"sessionStorage['{key}'] = '{value}'"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
def sessionstorage_delete(ctx: click.Context, key: str):
    """Delete a sessionStorage item.

    Examples:
        sessionstorage-delete "theme"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.execute_script("(key) => { sessionStorage.removeItem(key); }", key)

        json_output({"status": "success", "message": f"sessionStorage['{key}'] deleted"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def sessionstorage_clear(ctx: click.Context):
    """Clear all sessionStorage.

    Examples:
        sessionstorage-clear
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        session.controller.execute_script("() => sessionStorage.clear()")

        json_output({"status": "success", "message": "sessionStorage cleared"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def route_list(ctx: click.Context):
    """List all route patterns.

    Examples:
        route-list
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()

        if not hasattr(session, "_route_patterns"):
            session._route_patterns = []

        json_output(
            {
                "status": "success",
                "patterns": session._route_patterns,
                "count": len(session._route_patterns),
            }
        )
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


# Phase 7.5: DevTools and Debugging


@cli.command()
@click.pass_context
@click.argument("level", required=False, default="all")
def console(ctx: click.Context, level: str):
    """Get console logs.

    Examples:
        console
        console error
        console warning
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_safe(session)

        if page is None:
            json_output({"status": "error", "error": "Console logs require CDP protocol"})
            sys.exit(1)

        console_logs = session.controller.execute_script("""
            () => {
                if (window.__consoleLogs) {
                    return window.__consoleLogs;
                }
                return [];
            }
        """) or []

        if level != "all":
            console_logs = [log for log in console_logs if log.get("type") == level]

        json_output({
            "status": "success",
            "logs": console_logs,
            "count": len(console_logs),
            "level": level,
            "note": "Note: Historical console logs may not be available for CDP sessions."
        })

    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
def console_clear(ctx: click.Context):
    """Clear console logs.

    Examples:
        console-clear
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        session = cli_ctx.session_manager.require_current_session()
        page = get_page_safe(session)

        if page is None:
            json_output({"status": "error", "error": "Console clear requires CDP protocol"})
            sys.exit(1)

        session.controller.execute_script("""
            () => {
                if (window.__consoleLogs) {
                    window.__consoleLogs = [];
                }
            }
        """)

        json_output({"status": "success", "message": "Console cleared"})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.option("--limit", default=50, help="Number of requests to capture")
@click.option("--url-pattern", help="Filter by URL pattern")
def network(ctx: click.Context, limit: int, url_pattern: Optional[str]):
    """Get network requests.

    Examples:
        network
        network --limit 100
        network --url-pattern "api/*"
    """
    cli_ctx: CLIContext = ctx.obj

    try:
        page = get_page_safe(cli_ctx.session_manager.require_current_session())

        if page is None:
            json_output({"status": "error", "error": "Network monitoring requires CDP protocol"})
            sys.exit(1)

        requests = []
        for request in page.context.requests.all():
            if url_pattern and url_pattern not in request.url:
                continue
            requests.append(
                {
                    "url": request.url,
                    "method": request.method,
                    "resource_type": request.resource_type,
                    "response_status": request.response.status if request.response else None,
                }
            )
            if len(requests) >= limit:
                break

        json_output({"status": "success", "requests": requests, "count": len(requests)})
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("filename", required=False)
def pdf(ctx: click.Context, filename: Optional[str]):
    """Generate PDF from current page.

    Examples:
        pdf
        pdf --filename report.pdf
    """
    cli_ctx: CLIContext = ctx.obj
    config = cli_ctx.config

    try:
        import os

        page = get_page_or_error(cli_ctx.session_manager.require_current_session())

        output_path = filename or os.path.join(config.output_dir, "page.pdf")
        os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)

        pdf_data = page.pdf()

        with open(output_path, "wb") as f:
            f.write(pdf_data)

        file_size = os.path.getsize(output_path)
        success_output(
            f"PDF saved: {output_path}",
            path=output_path,
            filename=os.path.basename(output_path),
            size_bytes=file_size,
            size_kb=round(file_size / 1024, 2),
        )
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


@cli.command()
@click.pass_context
@click.option("--format", default="json", help="Output format: json, yaml")
def config_list(ctx: click.Context, format: str):
    """List current configuration.

    Examples:
        config-list
        config-list --format yaml
    """
    cli_ctx: CLIContext = ctx.obj
    config = cli_ctx.config

    config_data = config.to_dict()

    if format == "yaml":
        import yaml

        yaml_output = yaml.dump(config_data, default_flow_style=False)
        click.echo(yaml_output)
    else:
        json_output({"status": "success", "config": config_data})


@cli.command()
@click.pass_context
@click.argument("key")
def config_get(ctx: click.Context, key: str):
    """Get a configuration value.

    Examples:
        config-get timeout_action
        config-get browser
    """
    cli_ctx: CLIContext = ctx.obj
    config = cli_ctx.config

    if hasattr(config, key):
        value = getattr(config, key)
        json_output({"status": "success", "key": key, "value": value, "type": type(value).__name__})
    else:
        json_output(
            {
                "status": "error",
                "error": f"Unknown config key: {key}",
                "available_keys": list(config.to_dict().keys()),
            }
        )
        sys.exit(1)


@cli.command()
@click.pass_context
@click.argument("key")
@click.argument("value")
def config_set(ctx: click.Context, key: str, value: str):
    """Set a configuration value.

    Examples:
        config-set browser firefox
        config-set timeout_action 5000
        config-set output_dir ./output
    """
    cli_ctx: CLIContext = ctx.obj
    config = cli_ctx.config

    if not hasattr(config, key):
        json_output(
            {
                "status": "error",
                "error": f"Unknown config key: {key}",
                "available_keys": list(config.to_dict().keys()),
            }
        )
        sys.exit(1)

    current_type = type(getattr(config, key))

    try:
        if current_type is int:
            converted_value = int(value)
        elif current_type is bool:
            converted_value = value.lower() in ("true", "1", "yes")
        else:
            converted_value = value

        setattr(config, key, converted_value)

        json_output(
            {
                "status": "success",
                "message": f"Config set: {key} = {converted_value}",
                "key": key,
                "value": converted_value,
                "type": current_type.__name__,
            }
        )
    except ValueError as e:
        json_output(
            {"status": "error", "error": f"Invalid value for {key} ({current_type.__name__}): {e}"}
        )
        sys.exit(1)


@cli.command()
@click.pass_context
@click.option("--install", is_flag=True, help="Install skill package to ~/.claude/skills/nomo-e2e")
def skills(ctx: click.Context, install: bool):
    """Show skill package location or install it.

    This command helps you set up the NomoE2E skill package for AI agents.

    Examples:
        nomo-e2e skills
        nomo-e2e skills --install
    """
    import os
    from pathlib import Path

    try:
        package_dir = Path(__file__).resolve().parent.parent.parent
        skill_dir = package_dir / "nomo_e2e_skills"

        if not skill_dir.exists():
            skill_dir = package_dir.parent / "nomo_e2e_skills"

        if not skill_dir.exists():
            skill_dir = package_dir.parent / "src" / "nomo_e2e_skills"

        skill_md = skill_dir / "SKILL.md"

        if not skill_md.exists():
            json_output(
                {
                    "status": "error",
                    "error": "SKILL.md not found in package directory",
                }
            )
            sys.exit(1)

        if install:
            target_dir = Path.home() / ".claude" / "skills" / "nomo-e2e"
            target_dir.mkdir(parents=True, exist_ok=True)

            skill_source_dir = skill_md.parent
            for item in skill_source_dir.rglob("*"):
                if item.is_file():
                    rel_path = item.relative_to(skill_source_dir)
                    target_file = target_dir / rel_path
                    target_file.parent.mkdir(parents=True, exist_ok=True)
                    target_file.write_bytes(item.read_bytes())

            json_output(
                {
                    "status": "success",
                    "message": f"Skill package installed to {target_dir}",
                    "path": str(target_dir),
                    "skill_file": str(target_dir / "SKILL.md"),
                }
            )
        else:
            json_output(
                {
                    "status": "success",
                    "message": "Skill package location",
                    "path": str(skill_md.parent),
                    "skill_file": str(skill_md),
                    "instructions": "To install: nomo-e2e skills --install",
                }
            )
    except Exception as e:
        json_output({"status": "error", "error": str(e)})
        sys.exit(1)


def main():
    """Entry point."""
    cli(obj=None)


if __name__ == "__main__":
    main()
