use keyring::{Entry};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Runtime};
use tauri_plugin_store::StoreExt;
use tiny_http::{Response, Server};
use sha2::{Sha256, Digest};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use rand::Rng;
use rand::distr::Alphanumeric;

const STORE_FILE: &str = "connection.json";
const USER_INFO_KEY: &str = "user_info";
const TOKENS_STORE_FILE: &str = "tokens.json";
const REFRESH_TOKEN_STORE_KEY: &str = "refresh_token";
const AUTH_TOKEN_STORE_KEY: &str = "auth_token";
const KEYRING_SERVICE: &str = "stirling-pdf";
const KEYRING_TOKEN_KEY: &str = "auth-token";
const KEYRING_REFRESH_TOKEN_KEY: &str = "refresh-token";

pub const TOKENS_STORE_FILE_FOR_TESTS: &str = TOKENS_STORE_FILE;
pub const REFRESH_TOKEN_STORE_KEY_FOR_TESTS: &str = REFRESH_TOKEN_STORE_KEY;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UserInfo {
    pub username: String,
    pub email: Option<String>,
}

fn get_keyring_entry() -> Result<Entry, String> {
    if std::env::var("STIRLING_PDF_TEST_FORCE_AUTH_KEYRING_FAIL").is_ok() {
        return Err("Forced keyring failure for tests".to_string());
    }
    log::debug!("Creating keyring entry with service='{}' username='{}'", KEYRING_SERVICE, KEYRING_TOKEN_KEY);
    let entry = Entry::new(KEYRING_SERVICE, KEYRING_TOKEN_KEY)
        .map_err(|e| {
            log::error!("Failed to create keyring entry: {}", e);
            format!("Failed to access keyring: {}", e)
        })?;
    log::debug!("Keyring entry created successfully");
    Ok(entry)
}

pub fn get_refresh_token_keyring_entry() -> Result<Entry, String> {
    if std::env::var("STIRLING_PDF_TEST_FORCE_REFRESH_KEYRING_FAIL").is_ok() {
        return Err("Forced keyring failure for tests".to_string());
    }
    Entry::new(KEYRING_SERVICE, KEYRING_REFRESH_TOKEN_KEY)
        .map_err(|e| format!("Failed to access keyring: {}", e))
}

/// Returns Ok(true) when keyring round-tripped the token; Ok(false) otherwise (caller must fall through to disk).
pub fn try_save_refresh_token_to_keyring(token: &str) -> Result<bool, String> {
    match get_refresh_token_keyring_entry() {
        Ok(entry) => match entry.set_password(token) {
            Ok(_) => match entry.get_password() {
                Ok(saved) if saved == token => Ok(true),
                _ => Ok(false),
            },
            Err(_) => Ok(false),
        },
        Err(_) => Ok(false),
    }
}

#[tauri::command]
pub async fn save_auth_token(app_handle: AppHandle, token: String) -> Result<(), String> {
    let trimmed = token.trim();
    if trimmed.is_empty() {
        log::warn!("Attempted to save empty auth token");
        return Err("Token cannot be empty".to_string());
    }

    if trimmed.len() != token.len() {
        log::debug!("Auth token had surrounding whitespace; storing trimmed token");
    }

    // Try keyring first (works in environments where Credential Manager persists writes).
    // Any failure - including entry creation - falls through to the Tauri Store fallback
    // below, never an early return, so restricted environments still persist the token.
    match get_keyring_entry() {
        Ok(entry) => match entry.set_password(trimmed) {
            Ok(_) => {
                // Verify it persists. On managed Win10 Pro environments (GPO restricting
                // Credential Manager, AV/EDR rules, roaming-profile DPAPI quirks) the
                // read-back can return the wrong value or NoEntry even when set succeeded.
                match entry.get_password() {
                    Ok(saved) if saved == trimmed => {
                        // Clear any stale fallback copy so the keyring stays authoritative.
                        if let Ok(store) = app_handle.store(TOKENS_STORE_FILE) {
                            if store.get(AUTH_TOKEN_STORE_KEY).is_some() {
                                store.delete(AUTH_TOKEN_STORE_KEY);
                                let _ = store.save();
                            }
                        }
                        log::info!("Auth token saved to keyring");
                        return Ok(());
                    }
                    _ => {
                        log::info!("Keyring did not persist auth token - using Tauri Store fallback");
                    }
                }
            }
            Err(e) => {
                log::info!("Keyring set failed for auth token: {} - using Tauri Store fallback", e);
            }
        },
        Err(e) => {
            log::info!("Keyring entry unavailable for auth token: {} - using Tauri Store fallback", e);
        }
    }

    // Fallback to Tauri Store (same pattern as save_refresh_token)
    let store = app_handle
        .store(TOKENS_STORE_FILE)
        .map_err(|e| format!("Failed to access tokens store: {}", e))?;

    store.set(
        AUTH_TOKEN_STORE_KEY,
        serde_json::to_value(trimmed)
            .map_err(|e| format!("Failed to serialize token: {}", e))?,
    );

    store
        .save()
        .map_err(|e| format!("Failed to save tokens store: {}", e))?;

    log::info!("Auth token saved to Tauri Store (fallback)");
    Ok(())
}

#[tauri::command]
pub async fn get_auth_token(app_handle: AppHandle) -> Result<Option<String>, String> {
    // Try keyring first (production / unrestricted environments). Any failure -
    // including entry creation - falls through to the Tauri Store fallback below.
    match get_keyring_entry() {
        Ok(entry) => match entry.get_password() {
            Ok(token) => return Ok(Some(token)),
            Err(keyring::Error::NoEntry) => {
                log::debug!("No auth token in keyring, trying Tauri Store");
            }
            Err(e) => {
                log::warn!("Keyring error reading auth token: {} - trying Tauri Store", e);
            }
        },
        Err(e) => {
            log::warn!("Keyring entry unavailable for auth token: {} - trying Tauri Store", e);
        }
    }

    // Fallback to Tauri Store
    let store = app_handle
        .store(TOKENS_STORE_FILE)
        .map_err(|e| format!("Failed to access tokens store: {}", e))?;

    let token: Option<String> = store
        .get(AUTH_TOKEN_STORE_KEY)
        .and_then(|v| serde_json::from_value(v.clone()).ok());

    Ok(token)
}

#[tauri::command]
pub async fn clear_auth_token(app_handle: AppHandle) -> Result<(), String> {
    // Clear from keyring (best-effort). Entry creation failure must not block clearing
    // the disk fallback, otherwise a stale fallback token could survive sign-out.
    match get_keyring_entry() {
        Ok(entry) => match entry.delete_credential() {
            Ok(_) | Err(keyring::Error::NoEntry) => {}
            Err(e) => {
                log::warn!("Failed to delete keyring credential: {}. Attempting overwrite with empty token.", e);
                // Overwrite with an empty token so a stale value cannot be reused
                if let Err(e2) = entry.set_password("") {
                    log::warn!("Failed to overwrite keyring auth token: {}", e2);
                }
            }
        },
        Err(e) => {
            log::warn!("Keyring entry unavailable while clearing auth token: {} - clearing Tauri Store fallback only", e);
        }
    }

    // Clear from Tauri Store fallback
    let store = app_handle
        .store(TOKENS_STORE_FILE)
        .map_err(|e| format!("Failed to access tokens store: {}", e))?;

    store.delete(AUTH_TOKEN_STORE_KEY);

    store
        .save()
        .map_err(|e| format!("Failed to save tokens store: {}", e))?;

    Ok(())
}

#[tauri::command]
pub async fn save_refresh_token<R: Runtime>(app_handle: AppHandle<R>, token: String) -> Result<(), String> {
    log::info!("Saving refresh token - trying keyring first");

    match try_save_refresh_token_to_keyring(&token) {
        Ok(true) => {
            // Clear any stale fallback copy so the keyring stays authoritative.
            if let Ok(store) = app_handle.store(TOKENS_STORE_FILE) {
                if store.get(REFRESH_TOKEN_STORE_KEY).is_some() {
                    store.delete(REFRESH_TOKEN_STORE_KEY);
                    let _ = store.save();
                }
            }
            log::info!("Refresh token saved to keyring");
            return Ok(());
        }
        Ok(false) => {
            log::info!("Keyring did not persist refresh token - using Tauri Store fallback");
        }
        Err(e) => {
            log::info!("Keyring error for refresh token: {} - using Tauri Store fallback", e);
        }
    }

    // Fallback to Tauri Store (dev mode without code signing, or restricted environments)
    let store = app_handle
        .store(TOKENS_STORE_FILE)
        .map_err(|e| format!("Failed to access tokens store: {}", e))?;

    store.set(
        REFRESH_TOKEN_STORE_KEY,
        serde_json::to_value(&token)
            .map_err(|e| format!("Failed to serialize token: {}", e))?,
    );

    store
        .save()
        .map_err(|e| format!("Failed to save tokens store: {}", e))?;

    log::info!("Refresh token saved to Tauri Store (fallback)");
    Ok(())
}

#[tauri::command]
pub async fn get_refresh_token<R: Runtime>(app_handle: AppHandle<R>) -> Result<Option<String>, String> {
    // Try keyring first (production / unrestricted environments). Any failure -
    // including entry creation - falls through to the Tauri Store fallback below.
    match get_refresh_token_keyring_entry() {
        Ok(entry) => match entry.get_password() {
            Ok(token) => {
                log::info!("Refresh token retrieved from keyring");
                return Ok(Some(token));
            }
            Err(keyring::Error::NoEntry) => {
                log::debug!("No refresh token in keyring, trying Tauri Store");
            }
            Err(e) => {
                log::warn!("Keyring error reading refresh token: {} - trying Tauri Store", e);
            }
        },
        Err(e) => {
            log::warn!("Keyring entry unavailable for refresh token: {} - trying Tauri Store", e);
        }
    }

    // Fallback to Tauri Store (dev or restricted environments)
    let store = app_handle
        .store(TOKENS_STORE_FILE)
        .map_err(|e| format!("Failed to access tokens store: {}", e))?;

    let token: Option<String> = store
        .get(REFRESH_TOKEN_STORE_KEY)
        .and_then(|v| serde_json::from_value(v.clone()).ok());

    if token.is_some() {
        log::info!("Refresh token retrieved from Tauri Store");
    } else {
        log::info!("No refresh token found");
    }

    Ok(token)
}

#[tauri::command]
pub async fn clear_refresh_token<R: Runtime>(app_handle: AppHandle<R>) -> Result<(), String> {
    log::info!("Clearing refresh token from all storage");

    // Best-effort keyring clear; never blocks disk clear.
    match get_refresh_token_keyring_entry() {
        Ok(entry) => match entry.delete_credential() {
            Ok(_) | Err(keyring::Error::NoEntry) => {}
            Err(e) => {
                log::warn!("Failed to delete keyring refresh token: {}. Attempting overwrite with empty token.", e);
                if let Err(e2) = entry.set_password("") {
                    log::warn!("Failed to overwrite keyring refresh token: {}", e2);
                }
            }
        },
        Err(e) => {
            log::warn!("Keyring entry unavailable while clearing refresh token: {} - clearing Tauri Store fallback only", e);
        }
    }

    // Clear from Tauri Store fallback
    let store = app_handle
        .store(TOKENS_STORE_FILE)
        .map_err(|e| format!("Failed to access tokens store: {}", e))?;

    store.delete(REFRESH_TOKEN_STORE_KEY);

    store
        .save()
        .map_err(|e| format!("Failed to save tokens store: {}", e))?;

    log::info!("Refresh token cleared");
    Ok(())
}

#[tauri::command]
pub async fn save_user_info(
    app_handle: AppHandle,
    username: String,
    email: Option<String>,
) -> Result<(), String> {
    let user_info = UserInfo { username, email };

    let store = app_handle
        .store(STORE_FILE)
        .map_err(|e| format!("Failed to access store: {}", e))?;

    store.set(
        USER_INFO_KEY,
        serde_json::to_value(&user_info)
            .map_err(|e| format!("Failed to serialize user info: {}", e))?,
    );

    store
        .save()
        .map_err(|e| format!("Failed to save store: {}", e))?;

    Ok(())
}

#[tauri::command]
pub async fn get_user_info(app_handle: AppHandle) -> Result<Option<UserInfo>, String> {
    log::debug!("Retrieving user info");

    let store = app_handle
        .store(STORE_FILE)
        .map_err(|e| format!("Failed to access store: {}", e))?;

    let user_info: Option<UserInfo> = store
        .get(USER_INFO_KEY)
        .and_then(|v| serde_json::from_value(v.clone()).ok());

    Ok(user_info)
}

#[tauri::command]
pub async fn clear_user_info(app_handle: AppHandle) -> Result<(), String> {
    let store = app_handle
        .store(STORE_FILE)
        .map_err(|e| format!("Failed to access store: {}", e))?;

    store.delete(USER_INFO_KEY);

    store
        .save()
        .map_err(|e| format!("Failed to save store: {}", e))?;

    Ok(())
}

// Response types for Spring Boot login (self-hosted)
#[derive(Debug, Deserialize)]
struct SpringBootSession {
    access_token: String,
}

#[derive(Debug, Deserialize)]
struct SpringBootUser {
    username: String,
    email: Option<String>,
}

#[derive(Debug, Deserialize)]
struct SpringBootLoginResponse {
    session: SpringBootSession,
    user: SpringBootUser,
}

// Response types for Supabase login (SaaS)
#[derive(Debug, Deserialize)]
struct SupabaseUserMetadata {
    full_name: Option<String>,
}

#[derive(Debug, Deserialize)]
struct SupabaseUser {
    email: Option<String>,
    user_metadata: Option<SupabaseUserMetadata>,
}

#[derive(Debug, Deserialize)]
struct SupabaseLoginResponse {
    access_token: String,
    refresh_token: Option<String>,
    user: SupabaseUser,
}

#[derive(Debug, Serialize)]
pub struct LoginResponse {
    pub token: String,
    pub username: String,
    pub email: Option<String>,
    pub refresh_token: Option<String>,
}

/// Login command - makes HTTP request from Rust to bypass CORS
/// Supports both Supabase authentication (SaaS) and Spring Boot authentication (self-hosted)
#[tauri::command]
pub async fn login(
    server_url: String,
    username: String,
    password: String,
    mfa_code: Option<String>,
    supabase_key: String,
    saas_server_url: String,
) -> Result<LoginResponse, String> {
    // Detect if this is Supabase (SaaS) or Spring Boot (self-hosted)
    let is_supabase = server_url.trim_end_matches('/') == saas_server_url.trim_end_matches('/');

    // Create HTTP client with certificate bypass
    // This handles:
    // - Self-signed certificates
    // - Missing intermediate certificates
    // - Certificate hostname mismatches
    // Note: Rustls only supports TLS 1.2 and TLS 1.3
    let client = reqwest::Client::builder()
        .danger_accept_invalid_certs(true)
        .timeout(std::time::Duration::from_secs(30))
        .user_agent("StirlingPDF-Desktop/1.0 Tauri")
        .build()
        .map_err(|e| {
            log::error!("Failed to create HTTP client: {}", e);
            format!("Failed to create HTTP client: {}", e)
        })?;

    if is_supabase {
        // Supabase authentication flow
        let login_url = format!("{}/auth/v1/token?grant_type=password", server_url.trim_end_matches('/'));

        let request_body = serde_json::json!({
            "email": username,
            "password": password,
        });

        let response = client
            .post(&login_url)
            .header("Content-Type", "application/json;charset=UTF-8")
            .header("apikey", &supabase_key)
            .header("Authorization", format!("Bearer {}", supabase_key))
            .header("X-Client-Info", "supabase-js-web/2.58.0")
            .header("X-Supabase-Api-Version", "2024-01-01")
            .json(&request_body)
            .send()
            .await
            .map_err(|e| {
                let error_msg = e.to_string();
                let error_lower = error_msg.to_lowercase();
                log::error!("Supabase login network error: {}", e);

                // Detect TLS version mismatch
                if error_lower.contains("peer is incompatible") ||
                   error_lower.contains("protocol version") ||
                   error_lower.contains("peerincompatible") ||
                   (error_lower.contains("handshake") && (error_lower.contains("tls") || error_lower.contains("ssl"))) {
                    format!(
                        "TLS version not supported: The Supabase server appears to require an unsupported TLS version. \
                        Please contact support. Technical details: {}", e
                    )
                } else {
                    format!("Network error connecting to Supabase: {}", e)
                }
            })?;

        let status = response.status();

        if !status.is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            log::error!("Supabase login failed with status {}: {}", status, error_text);

            return Err(if status.as_u16() == 400 || status.as_u16() == 401 {
                "Invalid username or password".to_string()
            } else if status.as_u16() == 403 {
                "Access denied".to_string()
            } else {
                format!("Login failed: {}", status)
            });
        }

        // Parse Supabase response format
        let login_response: SupabaseLoginResponse = response
            .json()
            .await
            .map_err(|e| format!("Failed to parse Supabase response: {}", e))?;

        let email = login_response.user.email.clone();
        let username = login_response.user.user_metadata
            .as_ref()
            .and_then(|m| m.full_name.clone())
            .or_else(|| email.clone())
            .unwrap_or_else(|| username);

        Ok(LoginResponse {
            token: login_response.access_token,
            username,
            email,
            refresh_token: login_response.refresh_token,
        })
    } else {
        // Spring Boot authentication flow
        let login_url = format!("{}/api/v1/auth/login", server_url.trim_end_matches('/'));
        log::debug!("Spring Boot login URL: {}", login_url);

        let mut payload = serde_json::json!({
            "username": username,
            "password": password,
        });

        if let Some(code) = mfa_code
            .as_ref()
            .map(|c| c.trim())
            .filter(|c| !c.is_empty())
        {
            payload["mfaCode"] = serde_json::Value::String(code.to_string());
        }

        let response = client
            .post(&login_url)
            .json(&payload)
            .send()
            .await
            .map_err(|e| {
                let error_msg = e.to_string();
                let error_lower = error_msg.to_lowercase();
                log::error!("Spring Boot login network error: {}", e);

                // Detect TLS version mismatch (server using TLS 1.0/1.1)
                if error_lower.contains("peer is incompatible") ||
                   error_lower.contains("protocol version") ||
                   error_lower.contains("peerincompatible") ||
                   (error_lower.contains("handshake") && (error_lower.contains("tls") || error_lower.contains("ssl"))) {
                    format!(
                        "TLS version not supported: The server appears to be using TLS 1.0 or TLS 1.1, which are not supported by this desktop app. \
                        Please upgrade your server to use TLS 1.2 or higher, or use the web version of Stirling-PDF instead. \
                        Technical details: {}", e
                    )
                // Other TLS/SSL errors (certificate issues)
                } else if error_lower.contains("tls") || error_lower.contains("ssl") ||
                   error_lower.contains("certificate") || error_lower.contains("decrypt") {
                    format!(
                        "TLS/SSL connection error: This usually means the server has certificate issues. \
                        The desktop app accepts self-signed certificates, so this might be a TLS version issue. \
                        Technical details: {}", e
                    )
                } else if error_lower.contains("connection refused") {
                    format!("Connection refused: Server is not reachable at {}. Check if the server is running and the URL is correct.", login_url)
                } else if error_lower.contains("timeout") {
                    format!("Connection timeout: Server at {} is not responding. Check your network connection.", login_url)
                } else if error_lower.contains("dns") || error_lower.contains("resolve") {
                    format!("DNS resolution failed: Cannot resolve hostname. Check if the server URL is correct.")
                } else {
                    format!("Network error: {}", e)
                }
            })?;

        let status = response.status();
        log::debug!("Spring Boot login response status: {}", status);

        if !status.is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            log::error!("Spring Boot login failed with status {}: {}", status, error_text);

            if let Ok(error_json) = serde_json::from_str::<serde_json::Value>(&error_text) {
                let error_code = error_json
                    .get("error")
                    .and_then(|value| value.as_str())
                    .map(|value| value.to_string());

                if let Some(code) = error_code {
                    if code == "mfa_required" || code == "invalid_mfa_code" {
                        return Err(code);
                    }
                }
            }

            return Err(if status.as_u16() == 401 {
                "Invalid username or password".to_string()
            } else if status.as_u16() == 403 {
                "Access denied".to_string()
            } else {
                format!("Login failed: {}", status)
            });
        }

        // Parse Spring Boot response format
        let login_response: SpringBootLoginResponse = response
            .json()
            .await
            .map_err(|e| format!("Failed to parse Spring Boot response: {}", e))?;

        log::info!("Spring Boot login successful for user: {}", login_response.user.username);

        Ok(LoginResponse {
            token: login_response.session.access_token,
            username: login_response.user.username,
            email: login_response.user.email,
            refresh_token: None,
        })
    }
}

/// Generate PKCE code_verifier (random 43-128 character string)
fn generate_code_verifier() -> String {
    rand::rng()
        .sample_iter(Alphanumeric)
        .take(128)
        .map(char::from)
        .collect()
}

/// Generate PKCE code_challenge from code_verifier (SHA256 hash, base64url encoded)
fn generate_code_challenge(code_verifier: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(code_verifier.as_bytes());
    let hash = hasher.finalize();
    URL_SAFE_NO_PAD.encode(hash)
}

/// Opens the system browser for OAuth authentication with localhost callback server
/// Uses 127.0.0.1 (loopback) which is supported by Google OAuth with any port
/// Implements PKCE (Proof Key for Code Exchange) for secure OAuth flow
#[tauri::command]
pub async fn start_oauth_login(
    _app_handle: AppHandle,
    provider: String,
    auth_server_url: String,
    supabase_key: String,
    success_html: String,
    error_html: String,
) -> Result<OAuthCallbackResult, String> {
    log::info!("Starting OAuth login for provider: {} with auth server: {}", provider, auth_server_url);

    // Generate PKCE code_verifier and code_challenge
    let code_verifier = generate_code_verifier();
    let code_challenge = generate_code_challenge(&code_verifier);

    log::debug!("PKCE code_verifier generated: {} chars", code_verifier.len());
    log::debug!("PKCE code_challenge: {}", code_challenge);

    // Use port 0 to let OS assign an available port (avoids port reuse issues)
    // Supabase allows any localhost port via redirect_to parameter
    let server = Server::http("127.0.0.1:0")
        .map_err(|e| format!("Failed to create OAuth callback server: {}", e))?;

    let port = match server.server_addr() {
        tiny_http::ListenAddr::IP(addr) => addr.port(),
        #[cfg(unix)]
        tiny_http::ListenAddr::Unix(_) => {
            return Err("OAuth callback server bound to Unix socket instead of TCP port".to_string())
        }
    };

    let callback_url = format!("http://127.0.0.1:{}/callback", port);
    log::info!("OAuth callback URL: {}", callback_url);

    // Build OAuth URL with authorization code flow + PKCE
    // Note: Use redirect_to (not redirect_uri) to tell Supabase where to redirect after processing
    // Supabase handles its own /auth/v1/callback internally
    // prompt=select_account forces Google to show account picker every time
    let oauth_url = format!(
        "{}/auth/v1/authorize?provider={}&redirect_to={}&code_challenge={}&code_challenge_method=S256&prompt=select_account",
        auth_server_url.trim_end_matches('/'),
        provider,
        urlencoding::encode(&callback_url),
        urlencoding::encode(&code_challenge)
    );

    log::info!("Full OAuth URL: {}", oauth_url);
    log::info!("========================================");

    // Open system browser
    if let Err(e) = tauri_plugin_opener::open_url(&oauth_url, None::<&str>) {
        log::error!("Failed to open browser: {}", e);
        return Err(format!("Failed to open browser: {}", e));
    }

    // Wait for OAuth callback with timeout
    let result = Arc::new(Mutex::new(None));
    let result_clone = Arc::clone(&result);

    // Spawn server handling in blocking thread
    let server_handle = std::thread::spawn(move || {
        log::info!("Waiting for OAuth callback...");

        // Wait for callback (with timeout)
        for _ in 0..120 { // 2 minute timeout
            if let Ok(Some(request)) = server.recv_timeout(std::time::Duration::from_secs(1)) {
                let url_str = format!("http://127.0.0.1{}", request.url());
                log::debug!("Received OAuth callback: {}", url_str);

                // Parse the authorization code from URL
                let callback_data = parse_oauth_callback(&url_str);

                // Respond with appropriate HTML based on result
                let html_response = match &callback_data {
                    Ok(_) => {
                        log::info!("Successfully extracted authorization code");
                        success_html.clone()
                    }
                    Err(error_msg) => {
                        log::warn!("OAuth callback error: {}", error_msg);
                        // Replace {error} placeholder with actual error message
                        error_html.replace("{error}", error_msg)
                    }
                };

                let response = Response::from_string(html_response)
                    .with_header(tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/html; charset=utf-8"[..]).unwrap())
                    .with_header(tiny_http::Header::from_bytes(&b"Connection"[..], &b"close"[..]).unwrap());

                let _ = request.respond(response);

                // Store result and exit loop
                let mut result_lock = result_clone.lock().unwrap();
                *result_lock = Some(callback_data);
                break;
            }
        }
    });

    // Wait for server thread to complete
    server_handle.join()
        .map_err(|_| "OAuth callback server thread panicked".to_string())?;

    // Get result
    let callback_data = result.lock().unwrap().take()
        .ok_or_else(|| "OAuth callback timeout - no response received".to_string())?;

    // Handle the callback data - exchange authorization code for tokens
    match callback_data? {
        OAuthCallbackData::Code { code, redirect_uri } => {
            log::info!("OAuth completed with authorization code flow, exchanging code...");
            exchange_code_for_token(&auth_server_url, &code, &redirect_uri, &code_verifier, &supabase_key).await
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OAuthCallbackResult {
    pub access_token: String,
    pub refresh_token: Option<String>,
    pub expires_in: Option<i64>,
}

// Internal enum for handling authorization code flow
#[derive(Debug, Clone)]
enum OAuthCallbackData {
    Code { code: String, redirect_uri: String },
}

/// Exchange authorization code for access token using PKCE
async fn exchange_code_for_token(
    auth_server_url: &str,
    code: &str,
    _redirect_uri: &str,
    code_verifier: &str,
    supabase_key: &str,
) -> Result<OAuthCallbackResult, String> {
    log::info!("Exchanging authorization code for access token with PKCE");

    // Create HTTP client with certificate bypass
    // This handles:
    // - Self-signed certificates
    // - Missing intermediate certificates
    // - Certificate hostname mismatches
    // Note: Rustls only supports TLS 1.2 and TLS 1.3
    let client = reqwest::Client::builder()
        .danger_accept_invalid_certs(true)
        .timeout(std::time::Duration::from_secs(30))
        .user_agent("StirlingPDF-Desktop/1.0 Tauri")
        .build()
        .map_err(|e| {
            log::error!("Failed to create HTTP client: {}", e);
            format!("Failed to create HTTP client: {}", e)
        })?;
    // grant_type goes in query string, not body!
    let token_url = format!("{}/auth/v1/token?grant_type=pkce", auth_server_url.trim_end_matches('/'));

    // Body should be JSON with auth_code and code_verifier
    let body = serde_json::json!({
        "auth_code": code,
        "code_verifier": code_verifier,
    });

    log::debug!("Token exchange URL: {}", token_url);
    log::debug!("Code verifier length: {} chars", code_verifier.len());

    let response = client
        .post(&token_url)
        .header("Content-Type", "application/json")
        .header("apikey", supabase_key)
        .header("Authorization", format!("Bearer {}", supabase_key))
        .json(&body)
        .send()
        .await
        .map_err(|e| {
            let error_msg = e.to_string();
            let error_lower = error_msg.to_lowercase();
            log::error!("OAuth token exchange network error: {}", e);

            // Detect TLS version mismatch
            if error_lower.contains("peer is incompatible") ||
               error_lower.contains("protocol version") ||
               error_lower.contains("peerincompatible") ||
               (error_lower.contains("handshake") && (error_lower.contains("tls") || error_lower.contains("ssl"))) {
                format!(
                    "TLS version not supported: The authentication server appears to require an unsupported TLS version. \
                    Please contact support. Technical details: {}", e
                )
            } else {
                format!("Failed to exchange code for token: {}", e)
            }
        })?;

    let status = response.status();
    if !status.is_success() {
        let error_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());
        log::error!("Token exchange failed with status {}: {}", status, error_text);
        return Err(format!("Token exchange failed: {}", error_text));
    }

    // Parse token response
    let token_response: serde_json::Value = response
        .json()
        .await
        .map_err(|e| format!("Failed to parse token response: {}", e))?;

    log::info!("Token exchange successful");

    let access_token = token_response
        .get("access_token")
        .and_then(|v| v.as_str())
        .ok_or_else(|| "No access_token in token response".to_string())?
        .to_string();

    let refresh_token = token_response
        .get("refresh_token")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string());

    let expires_in = token_response
        .get("expires_in")
        .and_then(|v| v.as_i64());

    Ok(OAuthCallbackResult {
        access_token,
        refresh_token,
        expires_in,
    })
}

fn parse_oauth_callback(url_str: &str) -> Result<OAuthCallbackData, String> {
    // Parse URL to extract authorization code or error
    let parsed_url = url::Url::parse(url_str)
        .map_err(|e| format!("Failed to parse callback URL: {}", e))?;

    // Check for OAuth error first (error responses take precedence)
    let mut error = None;
    let mut error_description = None;
    let mut code = None;

    for (key, value) in parsed_url.query_pairs() {
        match key.as_ref() {
            "error" => error = Some(value.to_string()),
            "error_description" => error_description = Some(value.to_string()),
            "code" => code = Some(value.to_string()),
            _ => {}
        }
    }

    // If OAuth provider returned an error, fail immediately
    if let Some(error_code) = error {
        let error_msg = if let Some(description) = error_description {
            format!("OAuth authentication failed: {} - {}", error_code, description)
        } else {
            format!("OAuth authentication failed: {}", error_code)
        };
        log::error!("{}", error_msg);
        return Err(error_msg);
    }

    // If we have a code, return it
    if let Some(auth_code) = code {
        log::info!("Found authorization code in callback");

        // Reconstruct the redirect_uri (without query params) for token exchange
        let redirect_uri = if let Some(port) = parsed_url.port() {
            format!("{}://{}:{}{}",
                parsed_url.scheme(),
                parsed_url.host_str().unwrap_or("127.0.0.1"),
                port,
                parsed_url.path()
            )
        } else {
            format!("{}://{}{}",
                parsed_url.scheme(),
                parsed_url.host_str().unwrap_or("127.0.0.1"),
                parsed_url.path()
            )
        };

        return Ok(OAuthCallbackData::Code {
            code: auth_code,
            redirect_uri,
        });
    }

    // No authorization code or error found
    Err("No authorization code or error found in OAuth callback".to_string())
}
