"""Login screen — device code OAuth flow.

Guides the observer through the device code authentication:
  1. Request a device code from the server
  2. Display the code and verification URL
  3. Open the URL in a browser
  4. Poll for confirmation
  5. Store credentials and proceed

Layout:
  ┌──────────────────────────────────────┐
  │                                      │
  │          sibilance                   │
  │                                      │
  │    Your code:  WORD-WORD             │
  │                                      │
  │    Visit: https://sibilance.fly.dev  │
  │    /begin and enter this code.       │
  │                                      │
  │    Waiting for confirmation...       │
  │                                      │
  └──────────────────────────────────────┘
"""

from __future__ import annotations

import asyncio

from textual.app import ComposeResult
from textual.containers import Container
from textual.screen import Screen
from textual.widgets import Footer, Header, Static

from sibilance.api.types import DeviceCodeResponse
from sibilance.auth.credentials import Credentials, write_credentials


def _diagnose_connection_error(exc: Exception, server_url: str = "") -> str:
    """Unwrap httpx/anyio exception chains to find the real error."""
    # Walk the exception chain to find the root cause
    root = exc
    while root.__cause__ is not None:
        root = root.__cause__

    root_str = str(root)
    exc_str = str(exc)
    url_hint = f" ({server_url})" if server_url else ""

    # Check for SSL certificate errors (common on macOS)
    ssl_keywords = ("CERTIFICATE_VERIFY_FAILED", "certificate verify failed",
                    "ssl", "SSL")
    chain_str = f"{exc_str} {root_str} {type(root).__name__}"
    if any(kw in chain_str for kw in ssl_keywords):
        return (
            f"SSL certificate error{url_hint}.\n\n"
            "On macOS, run:\n"
            "  /Applications/Python\\ 3.x/Install\\ Certificates.command\n"
            "or: pip install certifi"
        )

    # If the root cause differs from the surface error, show both
    if root is not exc and root_str != exc_str:
        return f"Connection failed{url_hint}: {root_str}\n({type(root).__name__})"

    return f"Connection failed{url_hint}: {exc_str}"


class LoginScreen(Screen):
    """Device code login flow."""

    DEFAULT_CSS = """
    LoginScreen {
        layout: vertical;
        align: center middle;
    }

    #login-panel {
        width: 50;
        height: auto;
        padding: 2 4;
        background: $surface;
        border: solid $secondary;
        content-align: center middle;
    }

    #login-title {
        text-align: center;
        color: $primary;
        text-style: bold;
        padding: 1 0;
    }

    #login-code {
        text-align: center;
        color: $accent;
        text-style: bold;
        padding: 1 0;
    }

    #login-instructions {
        text-align: center;
        color: $text;
        padding: 0 0 1 0;
    }

    #login-status {
        text-align: center;
        color: $text-muted;
        text-style: italic;
        padding: 1 0;
    }

    #login-error {
        text-align: center;
        color: #ff6b6b;
        padding: 1 0;
    }
    """

    def __init__(self, **kwargs: object) -> None:
        super().__init__(**kwargs)
        self._device_code: DeviceCodeResponse | None = None

    def compose(self) -> ComposeResult:
        yield Header(show_clock=True)
        with Container(id="login-panel"):
            yield Static("sibilance", id="login-title")
            yield Static("", id="login-code")
            yield Static("", id="login-instructions")
            yield Static("Connecting...", id="login-status")
            yield Static("", id="login-error")
        yield Footer()

    def on_mount(self) -> None:
        self.run_worker(self._login_flow(), name="login")

    async def _login_flow(self) -> None:
        """Run the full device code login flow."""
        api = self.app.api  # type: ignore[attr-defined]

        # Step 1: Request device code
        try:
            self._update_status("Requesting login code...")
            self._device_code = await api.create_device_code()
        except Exception as exc:
            msg = _diagnose_connection_error(exc, api._server_url)
            self._update_error(msg)
            return

        dc = self._device_code

        # Step 2: Display code and URL
        try:
            self.query_one("#login-code", Static).update(
                f"[bold #ffd93d]{dc.code}[/]"
            )
            self.query_one("#login-instructions", Static).update(
                f"Visit {dc.verify_url}\nand enter this code."
            )
        except Exception:
            pass

        # Step 3: Open browser
        try:
            self.app.open_url(dc.verify_url)
            self._update_status("Opened browser. Waiting for confirmation...")
        except Exception:
            self._update_status("Open the URL above in your browser.\nWaiting for confirmation...")

        # Step 4: Poll for confirmation
        for _ in range(200):  # ~10 minutes at 3s intervals
            await asyncio.sleep(3.0)
            try:
                status = await api.poll_device_status(dc.device_id)
                if status.status == "confirmed" and status.token:
                    # Step 5: Store credentials
                    write_credentials(
                        Credentials(
                            token=status.token,
                            observer_id=status.observer_id or "",
                            display_name=status.display_name or "",
                        )
                    )
                    self._update_status("Authenticated!")
                    await asyncio.sleep(1.0)

                    # Proceed to load creature
                    await self.app._post_login()  # type: ignore[attr-defined]
                    return
                elif status.status == "expired":
                    self._update_error("Code expired. Restart to try again.")
                    return
            except Exception:
                pass  # Network hiccup, keep polling

        self._update_error("Login timed out. Restart to try again.")

    def _update_status(self, text: str) -> None:
        try:
            self.query_one("#login-status", Static).update(text)
        except Exception:
            pass

    def _update_error(self, text: str) -> None:
        try:
            self.query_one("#login-error", Static).update(text)
            self.query_one("#login-status", Static).update("")
        except Exception:
            pass

    def refresh_from_creature(self, creature: object) -> None:
        pass
