"""
ChatGPT Playwright Client - Browser automation for chat.openai.com

Follows patterns from:
- tools/fetch_tokens_firefox.py (profile reuse)
- helix_cdc/jazz/jazz_http.py (structured responses with timing)

IMPORTANT: ToS Implications
- OpenAI's Terms of Service may prohibit automated access
- Use for personal productivity tooling at your own risk
- This does NOT circumvent any authentication
"""

import os
import time
import asyncio
from pathlib import Path
from typing import Optional, Callable

from .models import (
    ChatGPTResponse,
    ChatGPTStatus,
    ChatGPTClientConfig,
    StreamChunk,
)
from .selectors import (
    PROMPT_TEXTAREA,
    SEND_BUTTON,
    LAST_ASSISTANT_MESSAGE,
    MESSAGE_CONTENT,
    STREAMING_INDICATOR,
    RATE_LIMIT_BANNER,
    LOGIN_REQUIRED,
)
from .stream_handler import StreamAccumulator
from .exceptions import (
    ChatGPTSessionExpired,
    ChatGPTDOMChanged,
)


class ChatGPTPlaywrightClient:
    """
    Playwright-based ChatGPT browser automation.

    Uses existing browser profile to reuse session cookies (no login automation).

    Usage:
        async with ChatGPTPlaywrightClient() as client:
            await client.navigate_to_chat()
            response = await client.send_prompt("Hello!")
            print(response.text)
    """

    def __init__(self, config: Optional[ChatGPTClientConfig] = None):
        self.config = config or ChatGPTClientConfig()
        self._browser = None
        self._context = None
        self._page = None
        self._playwright = None

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.close()

    async def connect(self) -> None:
        """Launch browser with existing profile (reuses session cookies)."""
        from playwright.async_api import async_playwright

        self._playwright = await async_playwright().start()

        # Profile path detection
        profile_path = self._resolve_profile_path()

        launch_args = []
        if profile_path:
            launch_args.append(f"--user-data-dir={profile_path}")

        # Disable automation detection flags
        launch_args.extend([
            "--disable-blink-features=AutomationControlled",
        ])

        self._browser = await self._playwright.chromium.launch(
            headless=self.config.headless,
            args=launch_args,
        )

        self._context = await self._browser.new_context(
            viewport={"width": 1280, "height": 800},
            user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        )

        self._page = await self._context.new_page()
        self._page.set_default_timeout(self.config.timeout_ms)

    def _resolve_profile_path(self) -> Optional[str]:
        """Resolve Chrome/Chromium profile path."""
        if self.config.profile_path:
            return str(Path(self.config.profile_path).expanduser())

        # Check environment variable
        env_profile = os.environ.get("CHROME_PROFILE_PATH")
        if env_profile:
            return str(Path(env_profile).expanduser())

        # Common profile locations
        candidates = [
            Path.home() / ".config/google-chrome",
            Path.home() / ".config/chromium",
            Path.home() / "snap/chromium/common/chromium",
        ]
        for candidate in candidates:
            if candidate.exists():
                return str(candidate)

        return None

    async def navigate_to_chat(self) -> None:
        """Navigate to ChatGPT and verify session."""
        await self._page.goto("https://chat.openai.com/", wait_until="networkidle")

        # Check for login requirement
        login_elem = await self._page.query_selector(LOGIN_REQUIRED)
        if login_elem:
            raise ChatGPTSessionExpired(
                "Login required. Please log in manually in your browser, "
                "then run this tool again."
            )

    async def send_prompt(
        self,
        prompt: str,
        on_chunk: Optional[Callable[[StreamChunk], None]] = None,
    ) -> ChatGPTResponse:
        """
        Send a prompt and wait for complete response.

        Args:
            prompt: The text to send to ChatGPT
            on_chunk: Optional callback for streaming chunks

        Returns:
            ChatGPTResponse with full text, timing, and metadata
        """
        start_time = time.perf_counter()
        accumulator = StreamAccumulator(on_chunk=on_chunk)

        try:
            # Check for rate limiting before sending
            rate_limit = await self._page.query_selector(RATE_LIMIT_BANNER)
            if rate_limit:
                return ChatGPTResponse(
                    status=ChatGPTStatus.RATE_LIMITED,
                    error_message="Rate limit detected before sending",
                )

            # Find and fill prompt textarea
            textarea = await self._page.wait_for_selector(
                PROMPT_TEXTAREA,
                timeout=self.config.timeout_ms
            )
            if not textarea:
                raise ChatGPTDOMChanged("Could not find prompt textarea")

            await textarea.fill(prompt)

            # Click send button
            send_btn = await self._page.wait_for_selector(SEND_BUTTON, timeout=5000)
            if not send_btn:
                raise ChatGPTDOMChanged("Could not find send button")

            await send_btn.click()

            # Wait for streaming to start (stop button appears)
            try:
                await self._page.wait_for_selector(
                    STREAMING_INDICATOR,
                    timeout=self.config.timeout_ms,
                    state="visible",
                )
            except Exception:
                # Might not see stop button if response is very fast
                pass

            # Poll for streaming text
            deadline = time.perf_counter() + (self.config.response_timeout_ms / 1000)

            while time.perf_counter() < deadline:
                # Get current response text
                text = await self._get_response_text()
                is_streaming = await self._is_streaming()

                accumulator.process_text(text, is_streaming)

                if not is_streaming and text:
                    # Streaming finished
                    accumulator.finalize()
                    break

                await asyncio.sleep(self.config.stream_poll_interval_ms / 1000)
            else:
                # Timeout
                accumulator.finalize()
                return ChatGPTResponse(
                    status=ChatGPTStatus.TIMEOUT,
                    text=accumulator.full_text,
                    chunks=accumulator.chunks,
                    ttft_ms=accumulator.ttft_ms,
                    total_time_ms=(time.perf_counter() - start_time) * 1000,
                    stream_duration_ms=accumulator.stream_duration_ms,
                    error_message="Response timeout exceeded",
                )

            total_time_ms = (time.perf_counter() - start_time) * 1000

            return ChatGPTResponse(
                status=ChatGPTStatus.SUCCESS,
                text=accumulator.full_text,
                chunks=accumulator.chunks,
                ttft_ms=accumulator.ttft_ms,
                total_time_ms=total_time_ms,
                stream_duration_ms=accumulator.stream_duration_ms,
                conversation_id=await self._extract_conversation_id(),
            )

        except ChatGPTSessionExpired:
            raise
        except ChatGPTDOMChanged:
            raise
        except Exception as e:
            total_time_ms = (time.perf_counter() - start_time) * 1000
            return ChatGPTResponse(
                status=ChatGPTStatus.ERROR,
                text=accumulator.full_text,
                chunks=accumulator.chunks,
                ttft_ms=accumulator.ttft_ms,
                total_time_ms=total_time_ms,
                error_message=str(e),
            )

    async def _get_response_text(self) -> str:
        """Extract current response text from DOM."""
        # Find the last assistant message
        msg_elem = await self._page.query_selector(LAST_ASSISTANT_MESSAGE)
        if not msg_elem:
            return ""

        # Find content within the message
        content_elem = await msg_elem.query_selector(MESSAGE_CONTENT)
        if content_elem:
            return await content_elem.inner_text()

        return await msg_elem.inner_text()

    async def _is_streaming(self) -> bool:
        """Check if response is still streaming (stop button visible)."""
        stop_btn = await self._page.query_selector(STREAMING_INDICATOR)
        return stop_btn is not None

    async def _extract_conversation_id(self) -> Optional[str]:
        """Extract conversation ID from URL if available."""
        url = self._page.url
        if "/c/" in url:
            return url.split("/c/")[-1].split("?")[0]
        return None

    async def close(self) -> None:
        """Clean up browser resources."""
        if self._page:
            await self._page.close()
        if self._context:
            await self._context.close()
        if self._browser:
            await self._browser.close()
        if self._playwright:
            await self._playwright.stop()
