use tauri::{AppHandle, Runtime};
use tauri_plugin_opener::OpenerExt;
use tauri_plugin_oauth::start;
use url::Url;
use oauth2::{
    basic::BasicClient, AuthUrl, ClientId, CsrfToken, PkceCodeChallenge, RedirectUrl, Scope,
    TokenResponse, TokenUrl,
};
use oauth2::reqwest::async_http_client;

// const CLIENT_ID: &str = "YOUR_CLIENT_ID_HERE"; 
const AUTH_URL: &str = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
const TOKEN_URL: &str = "https://login.microsoftonline.com/common/oauth2/v2.0/token";

#[tauri::command]
pub async fn start_auth_flow<R: Runtime>(app: AppHandle<R>, client_id: String) -> Result<String, String> {
    // 1. Start Localhost Server
    let (tx, rx) = std::sync::mpsc::channel();
    
    // tauri-plugin-oauth start() returns a port and stops server when callback received
    let port = start(move |url| {
        // Ignore favicon requests to avoid triggering early
        if url.contains("favicon.ico") {
            return;
        }
        let _ = tx.send(url);
    })
    .map_err(|e| format!("Failed to start oauth server: {}", e))?;

    let redirect_uri = format!("http://localhost:{}/auth/callback", port);

    // 2. Setup OAuth Client
    let client = BasicClient::new(
        ClientId::new(client_id),
        None, // No client secret for PKCE public client
        AuthUrl::new(AUTH_URL.to_string()).map_err(|e| e.to_string())?,
        Some(TokenUrl::new(TOKEN_URL.to_string()).map_err(|e| e.to_string())?),
    )
    .set_redirect_uri(RedirectUrl::new(redirect_uri.clone()).map_err(|e| e.to_string())?);

    // 3. Generate PKCE Challenge
    let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();

    // 4. Generate Auth URL
    let (auth_url, _csrf_token) = client
        .authorize_url(CsrfToken::new_random)
        .add_scope(Scope::new("User.Read".to_string()))
        .add_scope(Scope::new("Calendars.Read".to_string()))
        .set_pkce_challenge(pkce_challenge)
        .url();

    // 5. Open Browser
    app.opener().open_url(auth_url.as_str(), None::<&str>)
        .map_err(|e| format!("Failed to open browser: {}", e))?;

    // 6. Wait for Callback
    let received_url_str = rx.recv().map_err(|e| format!("Failed to receive auth code: {}", e))?;
    
    // 7. Parse Code from URL
    // Actually we need to parse the query params from received_url_str
    let parsed_url = Url::parse(&received_url_str).map_err(|e| e.to_string())?;
    let pairs: std::collections::HashMap<_, _> = parsed_url.query_pairs().into_owned().collect();
    
    if let Some(err) = pairs.get("error") {
        let desc = pairs.get("error_description").map(|s| s.as_str()).unwrap_or("No description");
        return Err(format!("OAuth Error: {} ({})", err, desc));
    }

    let code = pairs.get("code").ok_or_else(|| format!("No code in redirect callback. Received URL: {}", received_url_str))?;
    
    // 8. Exchange Code for Token
    let token_result = client
        .exchange_code(oauth2::AuthorizationCode::new(code.clone()))
        .set_pkce_verifier(pkce_verifier)
        .request_async(async_http_client)
        .await
        .map_err(|e| format!("Failed to exchange token: {}", e))?;

    let access_token = token_result.access_token().secret();
    
    // Save token? Or just return it.
    // Ideally we save it in key storage, but for MVP return it.
    
    Ok(access_token.clone())
}

#[tauri::command]
pub async fn get_calendar_events(token: String) -> Result<Vec<serde_json::Value>, String> {
    let client = reqwest::Client::new();
    let res = client
        .get("https://graph.microsoft.com/v1.0/me/calendarView")
        .bearer_auth(token)
        .query(&[
            ("startDateTime", chrono::Utc::now().to_rfc3339()),
            ("endDateTime", (chrono::Utc::now() + chrono::Duration::days(7)).to_rfc3339()),
            ("$select", "id,subject,start,end,location,onlineMeeting,bodyPreview,body,attendees".to_string())
        ])
        .header("Prefer", "outlook.timezone=\"UTC\"")
        .send()
        .await
        .map_err(|e| e.to_string())?
        .json::<serde_json::Value>()
        .await
        .map_err(|e| e.to_string())?;
        
    // Extract 'value' array
    if let Some(events) = res.get("value").and_then(|v| v.as_array()) {
        Ok(events.clone())
    } else {
        Ok(vec![])
    }
}
