"""OAuth authentication flows for Hikaflow CLI."""

from __future__ import annotations

import base64
import hashlib
import html
import http.server
import secrets
import socketserver
import time
import urllib.parse
import webbrowser
from dataclasses import dataclass
from typing import Optional, Tuple

import requests

from autodebug.config import get_api_base, store_tokens

# PKCE constants
PKCE_VERIFIER_LENGTH = 64  # 256 bits
PKCE_CHALLENGE_METHOD = "S256"

# Device flow constants
DEVICE_POLL_INTERVAL = 5  # seconds
DEVICE_CODE_TIMEOUT = 900  # 15 minutes


@dataclass
class PKCEPair:
    """PKCE code verifier and challenge pair."""
    verifier: str
    challenge: str


@dataclass
class CallbackResult:
    """Result from OAuth callback."""
    authorization_code: Optional[str] = None
    state: Optional[str] = None
    error: Optional[str] = None


def generate_pkce_pair() -> PKCEPair:
    """Generate a PKCE code verifier and challenge.

    The verifier is a cryptographically random string.
    The challenge is the base64url-encoded SHA256 hash of the verifier.

    Returns:
        PKCEPair with verifier and challenge
    """
    # Generate random bytes and encode as base64url (no padding)
    verifier_bytes = secrets.token_bytes(PKCE_VERIFIER_LENGTH)
    verifier = base64.urlsafe_b64encode(verifier_bytes).rstrip(b"=").decode("ascii")

    # Generate challenge as SHA256 hash of verifier, base64url encoded
    challenge_hash = hashlib.sha256(verifier.encode("ascii")).digest()
    challenge = base64.urlsafe_b64encode(challenge_hash).rstrip(b"=").decode("ascii")

    return PKCEPair(verifier=verifier, challenge=challenge)


def _create_callback_handler(result: CallbackResult):
    """Create a callback handler class with instance-specific result storage."""

    class PKCECallbackHandler(http.server.BaseHTTPRequestHandler):
        """HTTP handler for PKCE OAuth callback."""

        def log_message(self, format, *args):
            """Suppress HTTP server logs."""
            pass

        def do_GET(self):
            """Handle GET request from OAuth callback."""
            parsed = urllib.parse.urlparse(self.path)

            if parsed.path == "/callback":
                query = urllib.parse.parse_qs(parsed.query)

                if "code" in query:
                    result.authorization_code = query["code"][0]
                    result.state = query.get("state", [None])[0]
                    self._send_success_response()
                elif "error" in query:
                    result.error = query.get("error_description", query["error"])[0]
                    self._send_error_response(result.error)
                else:
                    result.error = "No authorization code received"
                    self._send_error_response(result.error)
            else:
                self.send_response(404)
                self.end_headers()

        def _send_success_response(self):
            """Send success HTML response."""
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.end_headers()

            response_html = """
            <!DOCTYPE html>
            <html>
            <head>
                <title>Hikaflow CLI - Login Successful</title>
                <style>
                    body {
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        height: 100vh;
                        margin: 0;
                        background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
                        color: #fff;
                    }
                    .container {
                        text-align: center;
                        padding: 40px;
                        background: rgba(255,255,255,0.05);
                        border-radius: 16px;
                        border: 1px solid rgba(255,255,255,0.1);
                    }
                    .success-icon {
                        font-size: 64px;
                        margin-bottom: 20px;
                    }
                    h1 { margin: 0 0 10px; }
                    p { color: #aaa; margin: 0; }
                </style>
            </head>
            <body>
                <div class="container">
                    <div class="success-icon">&#10003;</div>
                    <h1>Login Successful!</h1>
                    <p>You can close this window and return to the terminal.</p>
                </div>
            </body>
            </html>
            """
            self.wfile.write(response_html.encode("utf-8"))

        def _send_error_response(self, error_msg: str):
            """Send error HTML response with proper escaping."""
            self.send_response(400)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.end_headers()

            # HTML-escape the error message to prevent XSS
            safe_error = html.escape(error_msg)

            response_html = f"""
            <!DOCTYPE html>
            <html>
            <head>
                <title>Hikaflow CLI - Login Failed</title>
                <style>
                    body {{
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        height: 100vh;
                        margin: 0;
                        background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
                        color: #fff;
                    }}
                    .container {{
                        text-align: center;
                        padding: 40px;
                        background: rgba(255,255,255,0.05);
                        border-radius: 16px;
                        border: 1px solid rgba(255,255,255,0.1);
                    }}
                    .error-icon {{
                        font-size: 64px;
                        margin-bottom: 20px;
                        color: #ef4444;
                    }}
                    h1 {{ margin: 0 0 10px; }}
                    p {{ color: #aaa; margin: 0; }}
                    .error {{ color: #ef4444; margin-top: 10px; }}
                </style>
            </head>
            <body>
                <div class="container">
                    <div class="error-icon">&#10007;</div>
                    <h1>Login Failed</h1>
                    <p class="error">{safe_error}</p>
                    <p style="margin-top: 20px;">Please try again from the terminal.</p>
                </div>
            </body>
            </html>
            """
            self.wfile.write(response_html.encode("utf-8"))

    return PKCECallbackHandler


def _find_available_port(start: int = 8100, end: int = 8200) -> int:
    """Find an available port in the given range.

    Uses SO_REUSEADDR to allow immediate rebind, mitigating the TOCTOU race
    where another process grabs the port between our check and the server bind.
    """
    import socket

    for port in range(start, end):
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                s.bind(("127.0.0.1", port))
                return port
        except OSError:
            continue
    raise RuntimeError("No available ports found")


def pkce_browser_flow(api_base: Optional[str] = None) -> Tuple[bool, str]:
    """Perform browser-based PKCE OAuth flow.

    Opens browser to authenticate with Clerk, then receives callback
    with authorization code on local HTTP server.

    Args:
        api_base: Optional API base URL override

    Returns:
        Tuple of (success: bool, message: str)
    """
    if api_base is None:
        api_base = get_api_base()

    # Generate PKCE pair
    pkce = generate_pkce_pair()

    # Generate state for CSRF protection
    state = secrets.token_urlsafe(32)

    # Find available port and start local server
    port = _find_available_port()
    redirect_uri = f"http://127.0.0.1:{port}/callback"

    # Create result container for this specific flow
    result = CallbackResult()

    # Create handler class with this result container
    handler_class = _create_callback_handler(result)

    # Create server
    server = socketserver.TCPServer(("127.0.0.1", port), handler_class)
    server.timeout = 2  # short timeout for responsive countdown UI

    try:
        # Get authorization URL from API
        response = requests.get(
            f"{api_base.rstrip('/')}/v1/cli/pkce/authorize",
            params={
                "redirect_uri": redirect_uri,
                "state": state,
                "code_challenge": pkce.challenge,
                "code_challenge_method": PKCE_CHALLENGE_METHOD,
            },
            timeout=10,
        )

        if response.status_code != 200:
            return False, f"Failed to get authorization URL: {response.text}"

        auth_url = response.json().get("authorization_url")
        if not auth_url:
            return False, "No authorization URL received"

        # Open browser
        print("Opening browser for authentication...")
        webbrowser.open(auth_url)
        print("Waiting for authorization... (press Ctrl+C to cancel)")

        # Responsive wait loop with countdown (instead of single blocking call)
        total_timeout = 60
        start = time.time()

        while time.time() - start < total_timeout:
            server.handle_request()
            if result.authorization_code or result.error:
                break
            remaining = int(total_timeout - (time.time() - start))
            if remaining > 0:
                print(
                    f"\rWaiting for authorization... ({remaining}s remaining)  ",
                    end="",
                    flush=True,
                )

        print()  # newline after countdown

        if result.error:
            return False, f"Authorization failed: {result.error}"

        if not result.authorization_code:
            return False, "Browser authentication timed out. The browser tab may have been closed."

        # Verify state
        if result.state != state:
            return False, "State mismatch - possible CSRF attack"

        # Exchange code for tokens
        token_response = requests.post(
            f"{api_base.rstrip('/')}/v1/cli/pkce/token",
            json={
                "code": result.authorization_code,
                "code_verifier": pkce.verifier,
                "redirect_uri": redirect_uri,
                "state": state,  # Required for PKCE validation
            },
            timeout=10,
        )

        if token_response.status_code != 200:
            return False, f"Token exchange failed: {token_response.text}"

        token_data = token_response.json()

        # Store tokens
        store_tokens(
            access_token=token_data["access_token"],
            refresh_token=token_data["refresh_token"],
            expires_at=token_data["expires_at"],
        )

        return True, "Authentication successful!"

    except KeyboardInterrupt:
        print("\nAuthentication cancelled.")
        return False, "Authentication cancelled by user"

    except Exception as e:
        return False, f"Authentication failed: {str(e)}"

    finally:
        server.server_close()


def device_auth_flow(api_base: Optional[str] = None) -> Tuple[bool, str]:
    """Perform device authorization flow for headless environments.

    Displays a code for the user to enter at a verification URL,
    then polls for authorization completion.

    Args:
        api_base: Optional API base URL override

    Returns:
        Tuple of (success: bool, message: str)
    """
    if api_base is None:
        api_base = get_api_base()

    try:
        # Request device code
        response = requests.post(
            f"{api_base.rstrip('/')}/v1/cli/device/authorize",
            json={"client_id": "hikaflow-cli"},
            timeout=10,
        )

        if response.status_code != 200:
            return False, f"Failed to get device code: {response.text}"

        data = response.json()
        device_code = data["device_code"]
        user_code = data["user_code"]
        verification_uri = data["verification_uri"]
        expires_in = data.get("expires_in", DEVICE_CODE_TIMEOUT)
        interval = data.get("interval", DEVICE_POLL_INTERVAL)

        # Display instructions
        print()
        print("=" * 50)
        print("  Device Authorization Required")
        print("=" * 50)
        print()
        print(f"  1. Open: {verification_uri}")
        print(f"  2. Enter code: {user_code}")
        print()
        print("  Waiting for authorization...")
        print()

        # Poll for authorization
        start_time = time.time()
        while time.time() - start_time < expires_in:
            time.sleep(interval)

            poll_response = requests.get(
                f"{api_base.rstrip('/')}/v1/cli/device/token",
                params={"device_code": device_code},
                timeout=10,
            )

            if poll_response.status_code == 200:
                # Success!
                token_data = poll_response.json()

                # Store tokens
                store_tokens(
                    access_token=token_data["access_token"],
                    refresh_token=token_data["refresh_token"],
                    expires_at=token_data["expires_at"],
                )

                print("  Authorization successful!")
                print()
                return True, "Authentication successful!"

            elif poll_response.status_code == 400:
                error_data = poll_response.json()
                error = error_data.get("error", "")

                if error == "authorization_pending":
                    # Still waiting, continue polling
                    print(".", end="", flush=True)
                    continue
                elif error == "slow_down":
                    # Rate limited, increase interval (cap at 60s)
                    interval = min(interval + 5, 60)
                    continue
                elif error == "access_denied":
                    return False, "Authorization denied by user"
                elif error == "expired_token":
                    return False, "Device code expired. Please try again."
                else:
                    return False, f"Authorization failed: {error}"

            else:
                # Unexpected response
                continue

        return False, "Authorization timed out. Please try again."

    except requests.RequestException as e:
        return False, f"Network error: {str(e)}"
    except Exception as e:
        return False, f"Authentication failed: {str(e)}"
