"""Watch mode for monitoring production errors.

Uses polling to check for new errors and optionally triggers
automatic AI analysis when new errors appear.

Supports system desktop notifications on macOS, Linux, and Windows.
"""

from __future__ import annotations

import asyncio
import platform
import subprocess
from datetime import datetime
from typing import Optional, Set

import httpx
from rich.console import Console
from rich.panel import Panel

console = Console()


# ============================================================================
# Desktop Notifications
# ============================================================================


def send_desktop_notification(
    title: str,
    message: str,
    subtitle: Optional[str] = None,
    sound: bool = True,
) -> bool:
    """Send a system desktop notification.

    Supports macOS, Linux (with notify-send), and Windows (with PowerShell).

    Args:
        title: Notification title.
        message: Notification body.
        subtitle: Optional subtitle (macOS only).
        sound: Whether to play a sound.

    Returns:
        True if notification was sent successfully.
    """
    system = platform.system()

    # Sanitize inputs to prevent injection in AppleScript/PowerShell
    def _sanitize(s: str) -> str:
        """Remove characters that could break AppleScript/PowerShell string interpolation."""
        return s.replace("\\", "\\\\").replace('"', '\\"').replace("'", "\\'")

    _title = _sanitize(title)
    _message = _sanitize(message)
    _subtitle = _sanitize(subtitle) if subtitle else None

    try:
        if system == "Darwin":
            # macOS - use osascript
            script = f'display notification "{_message}"'
            script += f' with title "{_title}"'
            if _subtitle:
                script += f' subtitle "{_subtitle}"'
            if sound:
                script += ' sound name "Glass"'

            result = subprocess.run(
                ["osascript", "-e", script],
                capture_output=True,
                timeout=5,
            )
            return result.returncode == 0

        elif system == "Linux":
            # Linux - use notify-send (args are separate, not interpolated)
            args = ["notify-send", title, message]
            if sound:
                args.extend(["--hint", "string:sound-name:message-new-instant"])

            result = subprocess.run(args, capture_output=True, timeout=5)
            return result.returncode == 0

        elif system == "Windows":
            # Windows - use PowerShell toast notifications
            # Sanitize XML special chars for the toast XML payload
            import html as _html
            _xml_title = _html.escape(title)
            _xml_message = _html.escape(message)
            script = f"""
            [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
            [Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
            [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
            $xml = @"
            <toast>
                <visual>
                    <binding template="ToastText02">
                        <text id="1">{_xml_title}</text>
                        <text id="2">{_xml_message}</text>
                    </binding>
                </visual>
            </toast>
"@
            $XmlDocument = [Windows.Data.Xml.Dom.XmlDocument]::new()
            $XmlDocument.LoadXml($xml)
            $AppId = "Hikaflow"
            [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($AppId).Show($XmlDocument)
            """

            result = subprocess.run(
                ["powershell", "-Command", script],
                capture_output=True,
                timeout=10,
            )
            return result.returncode == 0

        else:
            # Unsupported platform
            return False

    except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
        # Silently fail - notifications are optional
        return False


def _notify_new_error(error: dict) -> None:
    """Send desktop notification for a new error."""
    error_id = error.get("id", "unknown")[:8]
    exception_type = error.get("exception_type", "Error")
    message = error.get("message", "No message")[:80]

    send_desktop_notification(
        title=f"Hikaflow: {exception_type}",
        message=message,
        subtitle=f"ID: {error_id}",
        sound=True,
    )


def _notify_fix_available(test_name: str, fix_description: str) -> None:
    """Send desktop notification when a fix is available."""
    send_desktop_notification(
        title="Hikaflow: Fix Available",
        message=f"Fix ready for {test_name}: {fix_description[:60]}",
        sound=True,
    )


def _notify_test_recovered(test_name: str) -> None:
    """Send desktop notification when a test recovers."""
    send_desktop_notification(
        title="Hikaflow: Test Recovered",
        message=f"{test_name} is now passing consistently",
        sound=False,  # Less urgent, no sound
    )


async def watch_errors(
    interval: int = 60,
    auto_analyze: bool = False,
    environment: Optional[str] = None,
    model: Optional[str] = None,
) -> None:
    """Watch for new production errors.

    Polls the Hikaflow API at regular intervals and displays new errors.
    Optionally triggers AI analysis for each new error.

    Args:
        interval: Polling interval in seconds.
        auto_analyze: Whether to auto-analyze new errors with AI.
        environment: Filter to specific environment.
        model: AI model to use for auto-analysis.
    """
    from autodebug.auth import load_config

    config = load_config()
    api_base = config.get("api_base", "https://get.hikaflow.com")
    token = config.get("token")

    if not token:
        console.print("[red]Not authenticated.[/] Run: hikaflow login")
        raise SystemExit(1)

    seen_errors: Set[str] = set()

    # Display watch mode banner
    console.print(Panel(
        f"[bold green]Watch Mode[/]\n\n"
        f"Checking for new errors every {interval}s\n"
        f"Environment: {environment or 'all'}\n"
        f"Auto-analyze: {'[green]enabled[/]' if auto_analyze else '[dim]disabled[/]'}\n\n"
        f"[dim]Press Ctrl+C to stop[/]",
        border_style="green",
    ))

    # Initial fetch to populate seen set
    initial_errors = await _fetch_errors(api_base, token, environment, limit=50)
    for e in initial_errors:
        seen_errors.add(e.get("id", ""))

    console.print(f"\n[dim]Watching... ({len(seen_errors)} existing errors indexed)[/]\n")

    # Set up agent if auto-analyze enabled
    agent = None
    if auto_analyze:
        try:
            from autodebug.agent.core import HikaflowDeps, create_agent
            agent = create_agent(model)
            deps = HikaflowDeps(
                api_base=api_base,
                token=token,
                project_path=".",
                environment=environment,
            )
        except ImportError:
            console.print("[yellow]Warning: pydantic-ai not installed. Auto-analyze disabled.[/]")
            auto_analyze = False

    try:
        while True:
            try:
                errors = await _fetch_errors(api_base, token, environment, limit=20)
                new_errors = [e for e in errors if e.get("id") not in seen_errors]

                for error in new_errors:
                    error_id = error.get("id", "unknown")
                    seen_errors.add(error_id)

                    # Display new error notification
                    _print_new_error(error)

                    # Auto-analyze if enabled
                    if auto_analyze and agent:
                        await _auto_analyze_error(agent, deps, error)

                # Show heartbeat every 5 cycles
                if not new_errors:
                    pass  # Silent when no new errors

            except httpx.RequestError as e:
                console.print(f"[red]Connection error: {e}[/]")
            except Exception as e:
                console.print(f"[red]Error: {e}[/]")

            await asyncio.sleep(interval)

    except KeyboardInterrupt:
        console.print("\n[dim]Watch mode stopped[/]")


async def watch_files(
    paths: list[str],
    on_change: Optional[callable] = None,
) -> None:
    """Watch local files for changes.

    Uses watchfiles for efficient, async file watching.

    Args:
        paths: List of paths/patterns to watch.
        on_change: Callback function when files change.
    """
    from watchfiles import Change, awatch

    console.print(Panel(
        f"[bold green]File Watch Mode[/]\n\n"
        f"Watching: {', '.join(paths)}\n\n"
        f"[dim]Press Ctrl+C to stop[/]",
        border_style="green",
    ))

    try:
        async for changes in awatch(*paths):
            for change_type, path in changes:
                change_name = {
                    Change.added: "added",
                    Change.modified: "modified",
                    Change.deleted: "deleted",
                }.get(change_type, "changed")

                console.print(f"[dim][{_timestamp()}][/] [green]{change_name}[/]: {path}")

                if on_change:
                    try:
                        await on_change(change_type, path)
                    except Exception as e:
                        console.print(f"[red]Error in callback: {e}[/]")

    except KeyboardInterrupt:
        console.print("\n[dim]File watch stopped[/]")


async def watch_and_debug(
    interval: int = 60,
    environment: Optional[str] = None,
    model: Optional[str] = None,
) -> None:
    """Watch for errors and automatically start debug sessions.

    A more aggressive version of watch_errors that starts full
    debug sessions for each new error.

    Args:
        interval: Polling interval in seconds.
        environment: Filter to specific environment.
        model: AI model to use.
    """
    from autodebug.auth import load_config
    from autodebug.prompts import confirm_action

    config = load_config()
    api_base = config.get("api_base", "https://get.hikaflow.com")
    token = config.get("token")

    if not token:
        console.print("[red]Not authenticated.[/] Run: hikaflow login")
        raise SystemExit(1)

    seen_errors: Set[str] = set()

    console.print(Panel(
        f"[bold green]Watch & Debug Mode[/]\n\n"
        f"New errors will trigger interactive debug sessions.\n"
        f"Checking every {interval}s\n\n"
        f"[dim]Press Ctrl+C to stop[/]",
        border_style="green",
    ))

    # Initial fetch
    initial_errors = await _fetch_errors(api_base, token, environment, limit=50)
    for e in initial_errors:
        seen_errors.add(e.get("id", ""))

    try:
        while True:
            errors = await _fetch_errors(api_base, token, environment, limit=10)
            new_errors = [e for e in errors if e.get("id") not in seen_errors]

            for error in new_errors:
                error_id = error.get("id", "unknown")
                seen_errors.add(error_id)

                _print_new_error(error)

                # Ask if user wants to debug
                if confirm_action(f"Debug error {error_id}?", default=True):
                    from autodebug.agent.core import run_agent

                    console.print("\n[bold]Starting debug session...[/]\n")

                    async for chunk in run_agent(
                        f"Debug error {error_id}: get details, view transcript, replay, and suggest a fix",
                        model=model,
                        api_base=api_base,
                        token=token,
                    ):
                        console.print(chunk, end="")

                    console.print("\n")

            await asyncio.sleep(interval)

    except KeyboardInterrupt:
        console.print("\n[dim]Watch & debug stopped[/]")


# --- Helper functions ---

async def _fetch_errors(
    api_base: str,
    token: str,
    environment: Optional[str],
    limit: int = 20,
) -> list[dict]:
    """Fetch errors from Hikaflow API."""
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"{api_base}/v1/prod-errors",
            headers={"Authorization": f"Bearer {token}"},
            params={
                "per_page": limit,
                "environment": environment,
            },
            timeout=30.0,
        )
        if response.status_code == 200:
            result = response.json()
            # Transform to the format watch consumers expect
            return [
                {
                    "id": e.get("error_id"),
                    "exception_type": e.get("exception_type", "Unknown"),
                    "message": e.get("exception_message", ""),
                    "environment": e.get("environment", "unknown"),
                    "last_seen": e.get("created_at"),
                }
                for e in result.get("errors", [])
            ]
        return []


def _print_new_error(error: dict, desktop_notify: bool = True) -> None:
    """Print notification for a new error.

    Args:
        error: Error data from API.
        desktop_notify: Whether to send desktop notification.
    """
    error_id = error.get("id", "unknown")
    exception_type = error.get("exception_type", "Error")
    message = error.get("message", "No message")[:60]
    environment = error.get("environment", "unknown")

    console.print()
    console.print(Panel(
        f"[bold red]{exception_type}[/]\n\n"
        f"{message}\n\n"
        f"[dim]ID: {error_id}[/]\n"
        f"[dim]Env: {environment}[/]",
        title=f"[{_timestamp()}] New Error",
        border_style="red",
    ))

    # Send desktop notification
    if desktop_notify:
        _notify_new_error(error)


async def _auto_analyze_error(agent: any, deps: any, error: dict) -> None:
    """Run automatic AI analysis on an error."""
    error_id = error.get("id", "unknown")

    console.print("\n[bold green]Auto-analyzing...[/]\n")

    # Load production_debug skill for auto-analysis
    try:
        from autodebug.agent.core import set_active_skill
        set_active_skill(deps, f"debug error {error_id}")
    except Exception:
        pass

    try:
        async with agent.run_stream(
            f"Briefly analyze error {error_id}: "
            "1) What is the root cause? "
            "2) What's the likely fix? "
            "Keep response under 200 words.",
            deps=deps,
        ) as response:
            async for chunk in response.stream_text():
                console.print(chunk, end="")

        console.print("\n")
    except Exception as e:
        console.print(f"[red]Analysis failed: {e}[/]")


def _timestamp() -> str:
    """Get current timestamp string."""
    return datetime.now().strftime("%H:%M:%S")
