"""Azure AD authentication flows for Navari CLI using MSAL.

Uses Microsoft Authentication Library (MSAL) for all OAuth flows,
providing automatic token caching and silent refresh via refresh tokens.
"""

import asyncio
import concurrent.futures
import html as html_mod
import webbrowser
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs, urlparse

import msal
import typer
from rich.console import Console

from navari_cli.constants import DEFAULT_REDIRECT_PORT, DEFAULT_REDIRECT_URI

console = Console()


def _build_msal_app(
    auth_config: dict, token_cache: msal.SerializableTokenCache | None = None
) -> msal.PublicClientApplication:
    """Create an MSAL PublicClientApplication from server auth config."""
    return msal.PublicClientApplication(
        client_id=auth_config["client_id"],
        authority=auth_config["authority"],
        token_cache=token_cache,
    )


def _extract_token(result: dict) -> tuple[str, str | None]:
    """Extract access token from MSAL result, raising on error."""
    if "access_token" in result:
        return result["access_token"], result.get("id_token")

    error = result.get("error", "unknown_error")
    error_desc = result.get("error_description", error)
    raise ValueError(f"Authentication failed: {error_desc}")


async def acquire_token_silent(
    auth_config: dict, token_cache: msal.SerializableTokenCache, username: str | None = None
) -> str | None:
    """Try to silently acquire a token using cached refresh token.

    Args:
        auth_config: Dict with keys: authority, client_id, scopes
        token_cache: MSAL token cache with persisted refresh tokens
        username: Filter to this account (avoids refreshing wrong user's token)

    Returns the access token if refresh succeeded, None otherwise.
    """
    app = _build_msal_app(auth_config, token_cache)
    accounts = app.get_accounts(username=username) if username else app.get_accounts()
    if not accounts:
        return None

    result = app.acquire_token_silent(
        scopes=auth_config["scopes"],
        account=accounts[0],
    )
    if result and "access_token" in result:
        return result["access_token"]
    return None


def _login_callback_html(success: bool, server_base_url: str) -> str:
    """Build the HTML page shown in the browser after the OAuth callback.

    Uses the same brand colors, fonts, logo, and dark-mode support as the
    Navari frontend login screen.  Assets are loaded from the server; an
    ``onerror`` fallback hides them gracefully if unreachable.
    """
    b = html_mod.escape(server_base_url.rstrip("/"), quote=True)  # normalize and sanitize
    if success:
        status_text = "Login Successful!"
        # --status-completed light hsl(70,100%,25%) ≈ #408000
        status_color = "#408000"
        status_color_dark = "#99cc00"
        subtitle = "You can close this window and return to the CLI."
    else:
        status_text = "Authentication Failed"
        # --destructive hsl(336,100%,41%) = #d0006f
        status_color = "#d0006f"
        status_color_dark = "#ff5ca8"
        subtitle = "Please try again or use device code flow."

    onerror = "this.style.display=&apos;none&apos;"
    logo_light = f'<img class="logo logo-light" src="{b}/logo-light.png" alt="Navari" onerror="{onerror}">' if b else ""
    logo_dark = f'<img class="logo logo-dark" src="{b}/logo-dark.png" alt="Navari" onerror="{onerror}">' if b else ""
    server_link = (
        f'<p class="server-link"><a href="{b}/" target="_blank" rel="noopener noreferrer">{b}/</a></p>'
        if b and success
        else ""
    )

    return f"""\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Navari – {"Login" if success else "Error"}</title>
{f'<link rel="icon" type="image/png" href="{b}/fav.png">' if b else ""}
<style>
  * {{ margin:0; padding:0; box-sizing:border-box; }}
  body {{
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                 Helvetica, Arial, sans-serif;
    display: flex; justify-content: center; align-items: center;
    min-height: 100vh;
    background: #ffffff; color: #3f4444;
    -webkit-font-smoothing: antialiased;
  }}
  .card {{
    text-align: center; padding: 48px 64px;
    background: #ffffff; border-radius: 12px;
    border: 1px solid #ebefee;
    box-shadow: 0 2px 12px rgba(0,0,0,0.06);
  }}
  .logo {{ height: 128px; width: auto; margin-bottom: 24px; }}
  .logo-dark {{ display: none; }}
  .brand {{ font-size: 2.25rem; font-weight: 700; margin-bottom: 32px; color: #3f4444; }}
  .status {{ font-size: 1.25rem; font-weight: 600; color: {status_color}; margin-bottom: 8px; }}
  .subtitle {{ color: #3f4444; font-size: 0.95rem; }}
  .server-link {{ margin-top: 20px; font-size: 0.9rem; }}
  .server-link a {{ color: #830051; text-decoration: none; }}
  .server-link a:hover {{ text-decoration: underline; }}
  .hint {{ margin-top: 16px; font-size: 0.8rem; color: #9db0ac; }}
  @media (prefers-color-scheme: dark) {{
    body {{ background: #1a1b1e; color: #f5f5f5; }}
    .card {{ background: #252629; border-color: #3f4444;
             box-shadow: 0 2px 12px rgba(0,0,0,0.3); }}
    .logo-light {{ display: none; }}
    .logo-dark {{ display: inline; }}
    .brand {{ color: #f5f5f5; }}
    .status {{ color: {status_color_dark}; }}
    .subtitle {{ color: #d4dbd9; }}
    .server-link a {{ color: #cc6699; }}
    .hint {{ color: #6b7b77; }}
  }}
</style>
</head>
<body>
<div class="card">
  {logo_light}\
  {logo_dark}
  <h1 class="brand">Welcome to Navari</h1>
  <p class="status">{status_text}</p>
  <p class="subtitle">{subtitle}</p>
  {server_link}
  <p class="hint">This window can be safely closed.</p>
</div>
</body>
</html>"""


async def browser_oauth_flow(
    auth_config: dict,
    token_cache: msal.SerializableTokenCache | None = None,
    server_url: str = "",
) -> tuple[str, str | None]:
    """Browser-based OAuth flow using MSAL's auth code flow with PKCE.

    Args:
        auth_config: Dict with keys: authority, client_id, scopes
        token_cache: Optional MSAL token cache for persisting refresh tokens
        server_url: Navari server URL, used to load the logo on the callback page

    Returns:
        Tuple of (access_token, id_token)
    """
    app = _build_msal_app(auth_config, token_cache)
    redirect_uri = auth_config.get("redirect_uri", DEFAULT_REDIRECT_URI)

    # Initiate auth code flow (MSAL handles PKCE internally)
    flow = app.initiate_auth_code_flow(
        scopes=auth_config["scopes"],
        redirect_uri=redirect_uri,
    )

    if "auth_uri" not in flow:
        raise ValueError(f"Failed to initiate auth flow: {flow.get('error_description', 'unknown error')}")

    # Start local HTTP server to receive the callback
    callback_params: dict = {}

    class CallbackHandler(BaseHTTPRequestHandler):
        def do_GET(self):
            nonlocal callback_params
            callback_params = dict(parse_qs(urlparse(self.path).query))
            callback_params = {k: v[0] if len(v) == 1 else v for k, v in callback_params.items()}
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            success = "code" in callback_params
            base = server_url.rstrip("/") if server_url else ""
            self.wfile.write(_login_callback_html(success, base).encode())

        def log_message(self, format, *args):
            pass

    try:
        server = HTTPServer(("localhost", DEFAULT_REDIRECT_PORT), CallbackHandler)
        server.socket.settimeout(1.0)
    except OSError as e:
        raise RuntimeError(
            f"Failed to start local server on port {DEFAULT_REDIRECT_PORT}: {e}. "
            "Port may be in use. Try using device code flow instead: --device-code"
        ) from e

    console.print("[bold cyan]Opening browser for login...[/bold cyan]")
    try:
        webbrowser.open(flow["auth_uri"])
    except Exception as e:
        console.print(f"[yellow]Could not open browser: {e}[/yellow]")
        console.print(f"[yellow]Please manually open: {flow['auth_uri']}[/yellow]")

    console.print("[dim]Waiting for login in browser... (Ctrl+C to cancel)[/dim]")
    try:
        for _ in range(300):
            try:
                server.handle_request()
            except OSError:
                pass
            if callback_params:
                break
            await asyncio.sleep(0.1)
        else:
            raise TimeoutError("Login timeout - no response from browser after 5 minutes")
    except KeyboardInterrupt:
        server.server_close()
        console.print("\n[yellow]Login cancelled[/yellow]")
        raise typer.Abort() from None
    finally:
        server.server_close()

    if "error" in callback_params:
        raise ValueError(f"Authentication error: {callback_params['error']}")

    # Complete the auth code flow — MSAL exchanges code for tokens and caches them
    result = app.acquire_token_by_auth_code_flow(flow, callback_params)
    return _extract_token(result)


async def password_flow(
    auth_config: dict,
    username: str,
    password: str,
    token_cache: msal.SerializableTokenCache | None = None,
) -> tuple[str, str | None]:
    """Resource Owner Password Credentials (ROPC) flow for non-interactive login.

    Useful for CI/automation when the user has no MFA and the tenant allows ROPC.

    Args:
        auth_config: Dict with keys: authority, client_id, scopes
        username: User's email or UPN
        password: User's password
        token_cache: Optional MSAL token cache for persisting refresh tokens

    Returns:
        Tuple of (access_token, id_token)
    """
    app = _build_msal_app(auth_config, token_cache)
    result = app.acquire_token_by_username_password(
        username=username,
        password=password,
        scopes=auth_config["scopes"],
    )
    return _extract_token(result)


async def device_code_flow(
    auth_config: dict, token_cache: msal.SerializableTokenCache | None = None
) -> tuple[str, str | None]:
    """Device code flow for headless environments.

    Args:
        auth_config: Dict with keys: authority, client_id, scopes
        token_cache: Optional MSAL token cache for persisting refresh tokens

    Returns:
        Tuple of (access_token, id_token)
    """
    app = _build_msal_app(auth_config, token_cache)
    flow = app.initiate_device_flow(scopes=auth_config["scopes"])

    if "user_code" not in flow:
        raise ValueError(f"Failed to initiate device flow: {flow.get('error_description', 'unknown error')}")

    console.print("\n[bold yellow]Device Code Authentication[/bold yellow]")
    console.print(f"1. Visit: [cyan]{flow['verification_uri']}[/cyan]")
    console.print(f"2. Enter code: [bold green]{flow['user_code']}[/bold green]\n")
    console.print("[dim]Waiting for authentication...[/dim]")

    # MSAL's acquire_token_by_device_flow polls synchronously; run in thread
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await asyncio.get_event_loop().run_in_executor(pool, lambda: app.acquire_token_by_device_flow(flow))

    return _extract_token(result)
